diff --git a/.github/workflows/merge-by-comments.yml b/.github/workflows/merge-by-comments.yml new file mode 100644 index 0000000000..1f76a66c84 --- /dev/null +++ b/.github/workflows/merge-by-comments.yml @@ -0,0 +1,15 @@ +name: Merge-by + +on: + pull_request: + types: [opened, ready_for_review] +jobs: + rfr_add_date: + name: "Post merge-by date as comment" + runs-on: ubuntu-latest + permissions: + pull-requests: write + steps: + - uses: zowe-actions/shared-actions/merge-by@main + with: + operation: "bump-dates" \ No newline at end of file diff --git a/.github/workflows/merge-by-table.yml b/.github/workflows/merge-by-table.yml new file mode 100644 index 0000000000..8129c44b34 --- /dev/null +++ b/.github/workflows/merge-by-table.yml @@ -0,0 +1,25 @@ +name: Merge-by + +on: + pull_request: + types: [opened, ready_for_review, converted_to_draft] + pull_request_review: + types: [submitted] + push: + branches: + - main + - next + workflow_dispatch: + schedule: + - cron: "0 11 * * *" +jobs: + rfr_add_date: + name: "Build table and notify users" + runs-on: ubuntu-latest + permissions: + discussions: write + pull-requests: write + steps: + - uses: zowe-actions/shared-actions/merge-by@main + with: + operation: "build-table" \ No newline at end of file diff --git a/README.md b/README.md index 1cdeba5a02..96ffb1047c 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ -# **Zowe CLI** +# Zowe CLI + [![codecov](https://codecov.io/gh/zowe/zowe-cli/branch/master/graph/badge.svg)](https://codecov.io/gh/zowe/zowe-cli) [![OpenSSF Best Practices](https://bestpractices.coreinfrastructure.org/projects/7204/badge)](https://bestpractices.coreinfrastructure.org/projects/7204) @@ -8,49 +9,56 @@ This repository also contains the Zowe Node Client SDK. The SDK lets you leverag
-## **Contents** +## Content + - [Documentation](#documentation) - - [Contribution Guidelines](#contribution-guidelines) - - [Building Zowe CLI From Source](#building-zowe-cli-from-source) - - [Installing Zowe CLI From Source](#installing-zowe-cli-from-source) + - [Contribution guidelines](#contribution-guidelines) + - [Building Zowe CLI from source](#building-zowe-cli-from-source) + - [Installing Zowe CLI from source](#installing-zowe-cli-from-source) - [Uninstalling Zowe CLI](#uninstalling-zowe-cli) - [Configuring Zowe CLI](#configuring-zowe-cli) - [Zowe Node Client SDK](#zowe-node-client-sdk) - - [Running System Tests](#running-system-tests) + - [Running system tests](#running-system-tests) - [FAQs](#frequently-asked-questions) - - [Project Structure and Governance](#project-structure-and-governance) + - [Project structure and governance](#project-structure-and-governance)
-## **Documentation** -For information about how to install, configure, and use Zowe CLI, see [Zowe CLI Quick Start Documentation](https://docs.zowe.org/stable/getting-started/cli-getting-started/). For more detailed instructions, see [Zowe CLI Documentation](https://docs.zowe.org/stable/user-guide/cli-using-usingcli/), which also includes examples and tutorials for how to contribute to Zowe CLI and develop CLI plug-ins. +## Documentation + +For information about how to install, configure, and use Zowe CLI, see [Zowe CLI Quick Start](https://docs.zowe.org/stable/getting-started/cli-getting-started/) documentation. For more detailed instructions, see [Zowe CLI](https://docs.zowe.org/stable/user-guide/cli-using-usingcli/) documentation, which also includes examples and tutorials for how to contribute to Zowe CLI and develop CLI plug-ins. -Engineering design documentation is contained in the 'docs' directory in this repository. To view the Web Help for all Zowe CLI commands and contributed plug-ins, see the [Zowe CLI Web Help](https://docs.zowe.org/stable/web_help/index.html). To view all locally accessible commands, run `zowe --help-web`. For more use cases and tutorials visit [Medium.com/zowe](https://medium.com/zowe). +Engineering design documentation is contained in the `docs` directory in this repository. To view the Web Help for all Zowe CLI commands and contributed plug-ins, see the [Zowe CLI Web Help](https://docs.zowe.org/stable/web_help/index.html). To view all locally accessible commands, run `zowe --help-web`. For more use cases and tutorials visit [Medium.com/zowe](https://medium.com/zowe).
-## **Contribution Guidelines** +## Contribution guidelines + The following information is critical to working with the code, running/writing/maintaining automated tests, developing consistent syntax in your plug-in, and ensuring that your plug-in integrates with Zowe CLI properly: -| For more information about ... | See: | +| For more information about | Go to | | ------------------------------ | ----- | -| General guidelines that apply to contributing to Zowe CLI and Plug-ins | [Contribution Guidelines](./CONTRIBUTING.md) | -| Conventions and best practices for creating packages and plug-ins for Zowe CLI | [Package and Plug-in Guidelines](./docs/PackagesAndPluginGuidelines.md)| -Guidelines for contributing to Zowe SDKs| [SDK Guidelines](./docs/SDKGuidelines.md) | -| Guidelines for running tests on Zowe CLI | [Testing Guidelines](./docs/TESTING.md) | -| Guidelines for running tests on the plug-ins that you build| [Plug-in Testing Guidelines](./docs/PluginTESTINGGuidelines.md) | +| General guidelines that apply to contributing to Zowe CLI and Plug-ins | [Contribution guidelines](./CONTRIBUTING.md) | +| Conventions and best practices for creating packages and plug-ins for Zowe CLI | [Package and plug-in guidelines](./docs/PackagesAndPluginGuidelines.md)| +Guidelines for contributing to Zowe SDKs| [SDK guidelines](./docs/SDKGuidelines.md) | +| Guidelines for running tests on Zowe CLI | [Testing guidelines](./docs/TESTING.md) | +| Guidelines for running tests on the plug-ins that you build| [Plug-in testing guidelines](./docs/PluginTESTINGGuidelines.md) | | Documentation that describes the features of the Imperative CLI Framework | [About Imperative CLI Framework](https://github.com/zowe/imperative/wiki) | -| Naming CLI commands and developing syntax | [Command Format Standards](./docs/CommandFormatStandards.md) | -Versioning conventions for Zowe CLI and Plug-ins| [Versioning Guidelines](./docs/MaintainerVersioning.md) | -| Miscellaneous tips for development | [Development Tips](./docs/DevelopmentTips.md) +| Naming CLI commands and developing syntax | [Command format standards](./docs/CommandFormatStandards.md) | +Versioning conventions for Zowe CLI and Plug-ins| [Versioning guidelines](./docs/MaintainerVersioning.md) | +| Miscellaneous tips for development | [Development tips](./docs/DevelopmentTips.md) -**Tip:** -- Visit our [Sample Plug-in repository](https://github.com/zowe/zowe-cli-sample-plugin) for example plug-in code. You can follow developer tutorials [here](https://docs.zowe.org/stable/extend/extend-cli/cli-devTutorials.html). +**Tip:** Visit our [Sample plug-in repository](https://github.com/zowe/zowe-cli-sample-plugin) for example plug-in code. Follow the [developer tutorials](https://docs.zowe.org/stable/extend/extend-cli/cli-devTutorials.html) for more tips.
-## **Building Zowe CLI From Source** -Zowe CLI requires NPM version 8 and Cargo version 1.72.0 (or newer) to build from source. Before proceeding, check your NPM version with `npm --version` and if it's older than 8.x, update with `npm install -g npm`. To check your version of Cargo, run `cargo --version`. Cargo can be installed using rustup: [https://rustup.rs/](https://rustup.rs/). To update Cargo, run the `rustup update` command. +## Building Zowe CLI from source + +Zowe CLI requires the NPM version bundled with the active LTS versions of NodeJS and Cargo version 1.72.0 (or newer) to build from source. + +Check your NPM version with `npm --version` and if it's older than 8.x, update with `npm install -g npm`. + +Check your Cargo version with `cargo --version`. Cargo can be installed using [rustup](https://rustup.rs/). To update Cargo, run the `rustup update` command. For developers using Linux, the following packages are required to build Zowe CLI from source: @@ -80,26 +88,28 @@ When you update `package.json` to include new dependencies, or when you pull cha npm update ``` -**Tip:** -- When necessary, you can run the install command again to update dependencies changed in `package.json`. +**Tip:** When necessary, run the install command again to update dependencies changed in `package.json`.
-## **Installing Zowe CLI From Source** -From your copy of this repository, after a build, navigate to the `packages/cli` directory, then issue the following command to install Zowe CLI from source: +## Installing Zowe CLI from source + +From your copy of this repository, after a build, navigate to the `packages/cli` directory, then install Zowe CLI from source: ``` npm install -g ``` -**Notes:** +**Notes:** + - Depending on how you configured npm on Linux or Mac, you might need to prefix the `npm install -g` command or the `npm uninstall -g` command with `sudo` to let npm have write access to the installation directory. - On Windows, the `npm install -g` command might fail several times due to an `EPERM` error. This appears to be a bug that npm documented in their GitHub issues. This behaviour does not appear to be specific to installing the Zowe CLI package. Unfortunately, the only solution that we know of is to issue the `npm cache clean` command and the `npm install -g` command repeatedly until it works.
-## **Uninstalling Zowe CLI** -From your local copy of this repository, issue the following command to uninstall Zowe CLI: +## Uninstalling Zowe CLI + +From your local copy of this repository, to uninstall Zowe CLI: ``` npm uninstall --global @zowe/cli @@ -107,49 +117,55 @@ npm uninstall --global @zowe/cli
-## **Configuring Zowe CLI** - -Zowe CLI configuration is made up of different **profiles**. The profiles contain the information that Zowe CLI needs to communicate with the mainframe system. For example, credentials and z/OSMF host name. If you try to use Zowe CLI functionality and you get an error message that Zowe CLI failed to load any profiles, see the `zowe profiles create --help` command for the group of commands that you are trying to use (if any) to initialize your configuration. +## Configuring Zowe CLI -The most fundamental Zowe CLI profile is a `zosmf` profile. Issue the following command to understand how to create a `zosmf` profile in Zowe CLI: +Zowe CLI team configuration is made up of different **profiles**. Each profile contains the information that Zowe CLI needs to communicate with the mainframe system, such as credentials and host name. -``` -zowe profiles create zosmf-profile --help -``` +The most fundamental Zowe CLI profile is a `zosmf` profile, and it is included when Zowe CLI initializes your team configuration. However, you must still add your specific connection information to complete the `zosmf` profile. To do so, update your `~/.zowe/zowe.config.json` configuration file with a text editor or an IDE (such as Visual Studio Code) on your computer. -After you create your profile, you can confirm that the properties of your profile can connect to and communicate with your mainframe system successfully by issuing the following command: +After you create and/or finalize your profile, confirm that the properties of your profile can connect to and communicate with your mainframe system successfully: ``` zowe zosmf check status ``` -For detailed information about creating service profiles, creating base profiles, or integrating with Zowe API ML, see [Using Zowe CLI](https://docs.zowe.org/stable/user-guide/cli-using-usingcli/). +For detailed information about creating profiles, or integrating with Zowe API ML, see the documentation in the [Using Zowe CLI](https://docs.zowe.org/stable/user-guide/cli-using-usingcli/) section of Zowe Docs. -**Tip:** -- When you confirm that your profile connects to and communicates with your mainframe system successfully, you can issue the same command at any time to verify the availability and status of the z/OSMF subsystem on your mainframe. +**Tip:** When you confirm that your profile connects to and communicates with your mainframe system successfully, issue the same command at any time to verify the availability and status of the z/OSMF subsystem on your mainframe.
-## **Zowe Node Client SDK** +## Troubleshooting Zowe CLI + +If you try to use Zowe CLI functionality and you get an error message that Zowe CLI failed to load any profiles, try issuing the following commands: + +- `zowe config report-env` to generate a report on the status of the key areas in your working environment. Address any problems indicated in the report. +- `zowe config edit` to open your `~/.zowe/zowe.config.json` configuration file in your system's default text editor. Fix any properties with incorrect values. +- `zowe config secure` to have Zowe CLI prompt for your secure configuration properties in case your secure values are incorrect in your configuration. + +**Note:** For these commands, use the `--global-config` option to update your global configuration or `--user-config` for your user configuration. + +## Zowe Node Client SDK The Zowe Node Client SDK consists of APIs that enable you to build client applications that interface with the mainframe. Use the APIs to build your own client applications or automation scripts, independent of Zowe CLI. -For information about downloading and getting started with the SDK, see the [Zowe Docs](https://docs.zowe.org/stable/user-guide/sdks-using). To view the Zowe Node.js SDK doc, see [Zowe SDK Docs](https://docs.zowe.org/stable/typedoc/index.html). +For information about downloading and getting started with the SDK, see the [Zowe Docs](https://docs.zowe.org/stable/user-guide/sdks-using). To view the Zowe Node.js SDK doc, see [Using Zowe SDKs](https://docs.zowe.org/stable/typedoc/index.html). -**Tip:** -- Alternatively, you can import Zowe CLI into your project to call the Node APIs. However, importing all of Zowe CLI will increase the size of your project. For example, use the following statement to import packages from Zowe CLI: +Alternatively, import Zowe CLI into your project to call the Node APIs. However, importing all of Zowe CLI increases the size of your project. For example, use the following statement to import packages from Zowe CLI: ``` import { } from @zowe/cli ``` - Where `` is the name of an interface that you populate (i.e. `IIssueParms`) or a function that submits requests (i.e `IssueCommand`). + `` + + - Name of an interface that you populate (i.e. `IIssueParms`), or a function that submits requests (i.e `IssueCommand`)
-### Example API Usage +### Example API usage -For example usage syntax, see the readme for each API package in this repository: +For example usage syntax, see the README for each API package in this repository: - [Provisioning](https://github.com/zowe/zowe-cli/tree/master/packages/provisioning): Provision middleware and resources such as IBM CICS, IBM Db2, IBM MQ, and more. - [z/OS Console](https://github.com/zowe/zowe-cli/tree/master/packages/zosconsole): Perform z/OS console operations. @@ -163,9 +179,11 @@ For example usage syntax, see the readme for each API package in this repository
-## **Running System Tests** +## Running system tests + +In addition to Node.js, you must have a means to execute `.sh` (bash) scripts, which are required for running integration tests. -In addition to Node.js, you must have a means to execute `.sh` (bash) scripts, which are required for running integration tests. On Windows, you can install "Git Bash" (bundled with the standard [Git](https://git-scm.com/downloads) installation - check "Use Git and Unix Tools from Windows Command Prompt" installation option). +On Windows, install "Git Bash", which is bundled with the standard [Git](https://git-scm.com/downloads) installation. Select the installation option **Use Git and Unix Tools from Windows Command Prompt**. After downloading/installing the prerequisites, ensure that you can execute the following commands and receive success responses: @@ -175,11 +193,11 @@ After downloading/installing the prerequisites, ensure that you can execute the 3. On Windows: `where sh` ``` -To run Zowe CLI system tests, you need a configured properties file with proper system information present. +To run Zowe CLI system tests, you need a configured properties file populated with proper system information. -A dummy properties file is present in the `__tests__/__resources__/properties folder`, `default_properties.yaml`. Using this file as a template, you should create a `custom_properties.yaml` file within the same directory. Git is configured to ignore all properties files in the properties folder, except for the `default_properties.yaml` file. If the `custom_properties.yaml` file cannot be found or loaded, an error with relevant details will be thrown when attempting to run tests. +A dummy properties file is available in the `default_properties.yaml` file in the `__tests__/__resources__/properties` folder. Using this file as a template, you should create a `custom_properties.yaml` file within the same directory. Git is configured to ignore all properties files in the properties folder, except for the `default_properties.yaml` file. If the `custom_properties.yaml` file cannot be found or loaded, an error with relevant details displays when attempting to run tests. -You can then run the system tests by issuing the following command: +Run the system tests: ``` npm run test:system @@ -187,34 +205,39 @@ npm run test:system
-**IMPORTANT!** Do not commit configured properties files because they contain security principles and other critical information. +**IMPORTANT!** Do not commit configured properties files to this repository because they contain security principles and other critical information.
-## **Frequently Asked Questions** +## Frequently asked questions -**How can I install Zowe CLI as a root user on Mac/Linux?** +### How can I install Zowe CLI as a root user on Mac/Linux? - - You can install the CLI as root so that all users can access the CLI without installing it individually on their user account. As the root user on Mac/Linux, issue the following command: + - Install the CLI as root so that all users can access the CLI without installing it individually on their user account. As the root user on Mac/Linux, issue the following command: ``` npm i -g @zowe/cli@latest --ignore-scripts ``` + **WARNING!** If you use this method, plug-ins that are installed as root can only be accessed as root. Users must install plug-ins on their user account or share all profiles/plugins/settings/logs with root. You also might encounter npm errors if you install as root. We recommend that Linux administrators implement a user/group environment where permissions can be more carefully controlled. -**What is the difference between V1 and V2?** +### What is the difference between Zowe V2 and V3? - - V2 uses **team profiles** and **deprecates the Secure Credential Store** (SCS) plug-in. + - V2 introduces **team profiles** and **deprecates the Secure Credential Store** (SCS) plug-in used in Zowe V1. - Connection details can be managed efficiently within one file, promoting a global configuration that can be shared across teams and mainframe services. For more information on how to use profiles, visit [Zowe Docs](https://docs.zowe.org/stable/user-guide/cli-using-using-team-profiles/). + - Connection details can be managed efficiently within one file, promoting a global configuration that can be shared across teams and mainframe services. For more information on how to use profiles, see [Team configurations](https://docs.zowe.org/stable/user-guide/cli-using-using-team-profiles/) in Zowe Docs. - Secure credential encryption is included in the core CLI. + - Secure credential encryption is included in the core CLI. + + - V3 includes the preceding features. Additionally, removes support for Zowe V1 profiles. + + - To upgrade from an older Zowe release, see [Migrating from Zowe Vx to Zowe V3](https://docs.zowe.org/stable/whats-new/zowe-v3-migratio3).
Don't see what you're looking for? Browse questions from the community or ask your own in the [Q&A section](https://github.com/zowe/zowe-cli/discussions/categories/q-a) of our repo. -## **Project Structure and Governance** +## Project structure and governance Zowe CLI is a component of the Zowe Open Mainframe Project, part of the Linux Foundation. diff --git a/__tests__/__packages__/cli-test-utils/package.json b/__tests__/__packages__/cli-test-utils/package.json index 955e3b2ebc..41c25d94fe 100644 --- a/__tests__/__packages__/cli-test-utils/package.json +++ b/__tests__/__packages__/cli-test-utils/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/cli-test-utils", - "version": "8.8.3", + "version": "8.10.1", "description": "Test utilities package for Zowe CLI plug-ins", "author": "Zowe", "license": "EPL-2.0", @@ -43,7 +43,7 @@ "devDependencies": { "@types/js-yaml": "^4.0.9", "@types/uuid": "^10.0.0", - "@zowe/imperative": "8.8.3" + "@zowe/imperative": "8.10.1" }, "peerDependencies": { "@zowe/imperative": "^8.0.0" diff --git a/docs/Design_for_selecting_auth_type.md b/docs/Design_for_selecting_auth_type.md new file mode 100644 index 0000000000..c7f2c9761a --- /dev/null +++ b/docs/Design_for_selecting_auth_type.md @@ -0,0 +1,411 @@ +# Order of authentication in Zowe Clients + +This document proposes a design to enable users to specify the order in which credentials are selected for authentication when multiple credentials are specified by the user. + +Users may not intentionally specify multiple credentials for the same operation. However, because configuration properties can be inherited from a base profile or within nested profiles, it is possible that multiple credentials may be available when a Zowe client attempts to make a connection to a mainframe service. + +## Use cases + +A user may use the same user and password to authenticate to most of their services. It makes sense to place that user & password in a base profile so that they are automatically available to every service. This reduces redundancy and reduces maintenance efforts. If one plugin requires a token for authentication, the user would store a token value within that plugin's profile. For that plugin, both the user & password and a token will be available when Zowe CLI attempts to make a connection to that service. For this example, the token is the right choice for this service. For historical reasons, Zowe always selects a user & password over a token when both are available. The use of user & password does not give the desired results. + +In another example, sites gradually deploy API-ML and can easily encounter another (but opposite) authentication issue. Sites login to APIML to obtain a token, which is then used to authenticate all future requests to services through API-ML. The API-ML token is typically stored in a base profile so that connections to all services are done through API-ML with its token. When a new service is brought on-line at a site, it is common for that service to not be immediately integrated with APIML. For at least a period of time, the site makes a direct connection to that service. The site adds user/password properties to that service's profile to authenticate that direct connection. Once again, both user & password and a token are available when Zowe attempts to connect to the service for that profile. In this case, the user & password are the right choice for this service. However, this is the opposite choice from the previous example. + +As these examples demonstrate, Zowe cannot simply change which authentication type should be given preference. It varies based on what a site is trying to do. That order might also change from one part of the customer's configuration to another. The preferred order in which credentials are chosen is further complicated when we consider that certificates may also be used by a site for some of its services. + +## General approach for a solution + +In this section we identify the key features that a solution would have to provide. + +- The order in which different types of authentication are used should be controlled by the user in the Zowe client configuration. + +- The user must be able to change that authentication order for different parts of the Zowe client configuration. + +- Zowe client logic must be enhanced to select the authentication type for a profile based on a user-specified preferred order. + +- Zowe client extenders (CLI plugins and ZE extensions) should **not** be able to alter the order of authentication types. The user should control that choice. + +## Detailed requirements + +- If a user does not specify the order of authentication, Zowe should use the historical order of authentication so that we do not introduce a breaking change. + +- Zowe has an order of precedence for obtaining property values (config file, environment variable, command line). While a command line option will override the same property stored in a config file, it should not alter the order of authentication. + + - For example, assume that a user has specified that a certificate should be used before a token. Assume that a certificate is supplied in a config file and a token is supplied on the command line. + + - The certificate should be used because the user-configured authentication order specifies that certificates should be used before tokens. + + - The token should not be used just because it was supplied on the command line. + +- Zowe should ignore any authentication order property that is specified in a environment variable (unlike most other connection properties). + + - A single environment variable property would override **every** authentication order property specified in **every** profile within the user's zowe.config.json file. The most likely customer use of this property will be to specify a different authentication order for different profiles. A single environment variable would likely defeat the primary purpose of enabling a user to specify a different authentication order for different profiles. + +- The authentication order **could** be specified on the command line. However, in the initial implementation of this feature, Zowe CLI will **NOT** implement a command line option for the authentication order. + + - Conceptually, specifying an authentication order as a command line option is a reasonable idea. However, implementing that behavior could involve modifications to dozens of CLI command handlers, which could increase our implementation effort and time-to-market significantly. + + - It is not clear whether a command line option for authentication order will be particularly valuable to customers. The authentication order would typically be static for a given profile. It is unlikely to change from one CLI command to another. + + - Therefore, we will **NOT** implement a command-line option for authentication order until customer demand makes such work a priority. + +- The authentication order only identifies the order in which Zowe chooses the **one** authentication method that will be used. If that first authentication method fails, Zowe will not make a second attempt to authenticate with any of the later authentication methods. + +- Once an authentication is selected, our logic should ensure that only that one type of authentication is placed into a session object. Thus, logic in down-stream Zowe code will not alter the order of authentication selection, simply by testing for the authentications from the session in a different order than the order that the user specified. + + - If we were to continue to allow multiple authentications to be placed into a session, we will have to re-work various functions to test for authentications in the desired order. We will also have to provide the object containing that well-defined order to each such function. This will increase the amount of code being changed, and thus increase the time to market and increase the probability of mistakes. + +- Zowe clients do not currently support AUTH_TYPE_CERT_PFX, so we cannot add it to a set of available authentications at this time. If AUTH_TYPE_CERT_PFX is implemented, it should be placed immediately after AUTH_TYPE_CERT_PEM in the default order. + +- Customers should be able to specify AUTH_TYPE_NONE in their preferred order of authentications. While it is not advisable to have no authentication, if a customer has a service that requires no authentication, the customer should be able to specify AUTH_TYPE_NONE at the top of their list of authentications applicable to that particular profile. + +- A customer should not have to specify every possible authentication in their ordered list of authentications. If a site only uses password and tokens, the customer should be able to specify only those two authentications in their list. + +- If a customer-specified list of authentications contains none of our supported authentications, a default order will be used. + +- The `--show-input-only` option should show the order of authentication as part of its displayed connection properties. + +## Historical behavior + +The objective of this feature is to enable users to define the order in which an authentication type is selected. However, when a user does not specify any such order, the default order should reflect past behavior. + +- The AbstractRestClient currently enforces an order of: + + - AUTH_TYPE_TOKEN + - AUTH_TYPE_BASIC + - AUTH_TYPE_BEARER + - AUTH_TYPE_CERT_PEM + +- Zowe classes other than AbstractRestClient (like AbstractSession) currently override the authentication order from AbstractRestClient into: + + - AUTH_TYPE_BASIC + - AUTH_TYPE_TOKEN + - AUTH_TYPE_BEARER + - AUTH_TYPE_CERT_PEM + +These selections of authentication should be maintained as the default selections for their respective classes to avoid introducing a breaking change. + +## Configuration enhancement + +A new profile property named **authOrder** should be created to enable users to specify their order of precedence for the authentication to be used when making a REST connection. The authOrder property should be treated like one of our key connection properties (like host and port). Thus authOrder would have the following characteristics: + +- It must be specified within a "properties" object. That "properties" object could reside in: + + - A base profile + + - A parent profile of a nested configuration. + + - Any service profile. + + - Any profile specific to a plugin (or VSCode extension) that supports a REST connction. For example an **endevor** profile could contain an **authOrder** property, but an **endevor-location** profile would not. + +- Our existing inheritance of connection properties should also apply to the inheritance of the authOrder property. + +- We should be able to use our logic for where and how the **rejectUnauthorized** property is handled as a model for how we handle the **authOrder** property. + +- A new property can be added to the **properties** of any profile. Since we are proposing that **authOrder** be handled by imperative functions, no CLI plugin or ZE extension will need modification for the **authOrder** property to be correctly handled. + +- Such a new item within the properties object will not currently be included in the zowe.schema.json file. An IntelliSense-like editor will not display **authOrder** as an option when a user starts to add a new property in a profile. + + - We do not want to require plugins/extensions to add **authOrder** to their profiles. Asking extenders to add AuthOrder to their set of options (and thus the schema) in the middle of a release's lifecycle can be a burden for those contributors. + + - As a result, we feel that losing IntelliSense is an acceptable compromise to avoid adding work to every extender. Users will lose a nice convenience, but the user's config will not be limited in any way at runtime. + +To represent a series of values, the **authOrder** property should be an array. The following example shows how a user could specify their desired authOrder. + +``` +"properties": { +    "host": ... , +    "port": ... , +    "rejectUnauthorized": ... , +    "authOrder": [ "basic", "token", "cert-pem" ] +} +``` + +The programmatic definition of authOrder would be: + +``` +authOrder: SessConstants.AUTH_TYPE_CHOICES[] +``` + +The current set of AUTH_TYPE_CHOICES are: + +- AUTH_TYPE_BASIC = "basic" + +- AUTH_TYPE_BEARER = "bearer" + +- AUTH_TYPE_TOKEN = "token" + +- AUTH_TYPE_CERT_PEM = "cert-pem" + +- AUTH_TYPE_NONE = "none" + +We should add a new AUTH_TYPE_CHOICE of: + +- AUTH_TYPE_SSH_KEY = "ssh-key" + +That addition would enable customers to also specify the authentication order of precedence for an SSH connection using an authOrder property. The only permissible values for an ssh connection would be "basic" and "ssh-key". Our ssh-handling logic will have to be modified to enforce that restriction and to honor the order. Conversely, our zosmf-handling logic would have to be modified to reject "ssh-key" in authOrder (or at least ignore it). If we choose not to implement authOrder for ssh at this time, we should at least create a design and implementation that can tolerate the addition of "ssh-key" at a later date. + +## Documentation Impact + +- We must describe the purpose and function of the new authOrder property. + +- We must describe where users can place the authOrder property. + +- We must describe the default order of authentication, when no authOrder property is supplied. + +- We must document that the new authOrder property name is a Zowe reserved word that should **NOT** be used by any extender. + +- We must notify extenders to guide their customers to supply an appropriate authOrder property if their extension needs a non-default order. + +## Edge cases that must be confirmed during implementation + +Every edge case was not included in the protype used to confirm the validity of this design document. The following edge cases must be confirmed and appropriate logic must be written during the implementation of this design. + +- As an alternative to user & password, a property named base64EncodedAuth can be used in a session. The new APIs must handle this alternative. + +- To login to APIML, a user supplies a user & password (or cert) and receives a token. The new APIs must determine the correct authentication type for such a session which kind of morphs its authentication type during the transaction. + +- Zowe Explorer currently inherits the same hard-coded order as the CLI from the client SDKs. The new authentication order APIs must be integrated into the APIs used by Zowe Explorer. We must confirm whether any additional logic changes must be made within Zowe Explorer itself. + +- TSO commands can create a sequence of actions, which use a user & password, receive a token from TSO, and then use that token for additional actions. This change in authentication type during the life of a single transaction may be self contained within the transaction and may not not influenced by either the existing hard-coded authentication order or the new user-controlled authentication order. This behavior must be confirmed and modifications made if necessary. + +## A new class must be created to support authOrder + +A new class located in: + +    [zowe-cli/packages/imperative/src/rest/src/session/AuthOrder.ts](https://github.com/zowe/zowe-cli/blob/master/packages/imperative/src/rest/src/session/AuthOrder.ts) + +should be implemented to detect the authentication order supplied by a user, and place the correct set of credentials into a session. The class currently exists as a workable proof of concept. With no confirmation of the many edge cases, it does appear to accomplished the basic needs. It can be finalized once it is fully integrated with existing Zowe logic. + +This class is declared **@internal** which will limit its use to the imperative package. More access can be provided during implementation if we identify additional places in Zowe code that need to use functions from this class. + +Functions in the AuthOrder class include: + +- setTopDefaultAuth - This function will set the top authentication choice (either basic or token) when a default authOrder must be created because the user did not supply an auth order. Through the use of this function we can maintain backward compatibility and not introduce a breaking change. + + When authOrder has not been configured into zowe.config.json, we create a default order to be backward compatible. + + - If an argument of AUTH_TYPE_TOKEN is supplied, it should place token at the top of the order, resulting in this order: + + - AUTH_TYPE_TOKEN + + - AUTH_TYPE_BASIC + + - AUTH_TYPE_BEARER + + - AUTH_TYPE_CERT_PEM + + - AUTH_TYPE_NONE + + - If AUTH_TYPE_BASIC is supplied (or this function is not called), it should place basic at the top of the order, resulting in this order: + + - AUTH_TYPE_BASIC + + - AUTH_TYPE_TOKEN + + -  AUTH_TYPE_BEARER + + - AUTH_TYPE_CERT_PEM + + - AUTH_TYPE_NONE + + - It should be called from AbstractRestClient.constructor to replace its hard-coded array of credential types. + + - It might be called from other existing locations as needed. + +- putTopAuthInSession - This function will find the highest auth type (according to the authOrder) which exists in either the supplied session config or command line arguments, place the credentials associated with that auth type into the supplied session config, and remove the credentials for all other auth types from the supplied session config. + + - It might be called from ConnectionPropsForSessCfg.addPropsOrPrompt. + + - By temporarily modifying a local copy of addPropsOrPrompt to call putTopAuthInSession, I confirmed that the resulting session object contained only those credentials obtained from the command arguments associated with highest authentication type (as determined from the authOrder). + + - It might be called from ConnectionPropsForSessCfg.resolveSessCfgProps. + + - It might be called from ProfileInfo.createSession. + + - While apps like ZE do not take command line arguments, they do form an ICommandArguments object. The createSesion function already has such an object, so it can be passed to putTopAuthInSession. + + - Any other location in ZE-related code which needs to call putTopAuthInSession will need to form an ICommandArguments object. + + - It might be called from other existing locations as needed. + +- cacheAuthOrder - This function stores the authOrder property from the supplied command arguments for later use. In the prototype, it is only called from putTopAuthInSession. However, cacheAuthOrder is available should we find that we need to to call it from other locations (like at the end of the CommandProcessor.prepare function). + +- removeExtraCredsFromSess - This function removes all credential properties from the supplied session except for the top available authentication type. In the prototype, it is only called from putTopAuthInSession. However, removeExtraCredsFromSess is available should we find that we must scrub unneeded credentials from a session in other location in existing Zowe code. + +## Functions that reference AUTH_TYPE may need modification + +The set of candidates for modification consist of all functions that contain the string ***"AUTH_TYPE_"***. This section contains an assessment of whether each such function affects the authentication order and must be modified. + +- cli\src\config\auto-init\ApimlAutoInitHandler + + - doAutoInit - This function logins into APIML with the session object if either user & password or cert are in the session object. doAutoInit does not make a selection of order. However, it is passed an AbstractSession object. The session credentials should already have been placed into the AbstractSession by a previously called function + - **Modify doAutoInit ? No** + +- core\src\rest\ZosmfRestClient.ts + + - processError - This function just alters error message text based on authentications found in the session. If only the top authentication type resides in a session, then no need to change. + - **Modify processError ? No** + +- imperative\src\config\src\ConfigAutoStore.ts + + - _fetchTokenForSessCfg - Since this function is used to explicitly retrieve a token value to be auto-stored into a session config, its use of AUTH_TYPE_TOKEN does not affect the auth order. So, no need to change. + - **Modify _fetchTokenForSessCfg ? No** + +- imperative\src\imperative\src\config\cmd\import\import.handler.ts + + - buildSession - This function is used to import a config from a URL. That URL is an arbitrary location at a customer site where a config file is kept. It is not the target of a REST request to a mainframe service. By design, the only authentication that it will use is user & password. Supporting more authentication types in the 'import' command is beyond the scope of this authentication-order feature. Therefore, no need to change. + - **Modify buildSession ? No** + +- imperative\src\rest\src\client\AbstractRestClient.ts + + This class is the only class to use the recently created ISession.authTypeOrder property, which is an array of authentication types supplied in the order in which they should be selected. + + - buildOptions - This function tests for the authentication based on the order in which they occur in ISession.authTypeOrder. Therefore, no need to change. + + - **Modify buildOptions ? No** + + - constructor - This function currently hard-codes an order of authentication types into the ISession.authTypeOrder array contained the supplied session. After AuthOrder is implemented, the ISession.authTypeOrder array should already be set in the session which is passed as an argument to this constructor. We should remove the logic that hard-codes the authTypeOrder into the session. + + - **Modify constructor? yes** + + - Each of the following functions reference AUTH_TYPE_XXX to a place an identified type into the ISession.type property of the session. Since buildOptions calls just one of the following functions based on being the first available authentication in the ISession.authTypeOrder array, none of these functions need to change. Note that these functions may no longer be necessary since AuthOrder.putTopAuthInSession should have already set the ISession.type property. + + - **Modify setBearerAuth ? No** + + - **Modify setCertPemAuth ? No** + + - **Modify setPasswordAuth ? No** + + - **Modify setTokenAuth ? No** + +- imperative\src\rest\src\session\AbstractSession.ts + + - buildSession - This private function is called by the constructor, which accepts an Isession object. A caller could populate multiple authentications (and related properties) into that supplied session. Ideally the caller of the Session.constructor will have already called ConnectionPropsForSessCfg.addPropsOrPrompt() which will have already removed all but the highest priority available authentication from the session. We should confirm that this always true. If not, buildSession may have to call AuthOrder.putTopAuthInSession. We should also check whether assignments to session properties by buildSession will override any of the properties set by AuthOrder.putTopAuthInSession. + + - **Modify buildSession ? Maybe** + + - DEFAULT_TYPE - This is simply a constant definition set to AUTH_TYPE_NONE. It is not used in any CLI or ZE code outside of this AbstractSession class. Because it is a public property, it cannot be removed without risk of breaking change. If AUTH_TYPE_NONE is added to the ISession.authTypeOrder array, DEFAULT_TYPE should be deprecated. + + - **Modify DEFAULT_TYPE ? Yes** + +- imperative\src\rest\src\session\ConnectionPropsForSessCfg.ts + + - addPropsOrPrompt - Near the end of this function, a call to AuthOrder.putTopAuthInSession will ensure that the right authentication resides in the session. + + - **Modify addPropsOrPrompt ? Yes** + + - resolveSessCfgProps - Several functions call this function before creating a new session. Either this function must call AuthOrder.putTopAuthInSession, or each caller of resolveSessCfgProps must first call putTopAuthInSession. + + - **Modify resolveSessCfgProps ? Maybe** + + - setTypeForTokenRequest - This function handles setting authentication to AUTH_TYPE_TOKEN to get a token back from user & password. This does not appear to require any change, but it should be revisited after resolveSessCfgProps is refactored. + + - **Modify setTypeForTokenRequest ? Maybe** + +- imperative\src\rest\src\session\SessConstants.ts + + - Constants and type definitions of AUTH_TYPE_XXX are what they need to be. + - **Modify constants ? No** + +- imperative\src\rest\src\session\Session.ts + + - createFromUrl - This function is only called from ImportHandler.buildSession to enable importing a config from a URL. As with ImportHandler.buildSession, the use of AUTH_TYPE_BASIC when user & password exist is appropriate and should not need to change. + - **Modify createFromUrl ? No** + +- imperative\src\rest\src\session\doc\IOptionsForAddConnProps.ts + + - supportedAuthTypes - Our set of supported authentications will not change as part of this feature. + - **Modify supportedAuthTypes ? No** + +- imperative\src\rest\src\session\doc\ISession.ts + + - authTypeOrder - This property is used to hold the order of authentication types. It is currently populated by hard-coded login and is only used in AbstractRestClient.constructor & AbstractRestClient.buildOptions. There is no reason to change this property. AuthOrder.putTopAuthInSession should populate authTypeOrder's set of values based on customer input. The only change for authTypeOrder will be to remove the comment that states that this property is hard-coded. + + - **Modify authTypeOrder ? Yes** + +- packages\zosuss\src\SshBaseHandler.ts + + - process - This function explicitly sets a property named **supportedAuthTypes** to AUTH_TYPE_BASIC. It is unclear why there is no option in this logic to use an ssh-key. + - **Modify process ? Maybe** + + packages\zosuss\src\Shell.ts + + - connect - This function explicitly checks for an ssh key (first) or a password (second) in a hard-coded fashion. If we want the user's authOrder to apply to ssh connections, this function must call the proposed AuthOrder.putTopAuthInSession to to make the right authentication choice. + - **Modify connect ? Yes** + +## Functions that reference rejectUnauthorized may need modification + +If we treat **authOrder** like other connection properties, those functions that process **rejectUnauthorized** may also need to process **authOrder**. This section contains an assessment of whether each such function must be modified. + +- packages\cli\src\config\auto-init\ApimlAutoInitHandler.ts + + - doAutoInit - This function was analyzed in the previous section of this document. + - **Modify doAutoInit ? No** + +- packages\cli\src\imperative.ts + + - This class provides definitions used to create the Zowe command tree and other CLI operating properties. It includes command-line options for connection properties (like host, port, user, and password). We must add **authOrder** as a new command line option into this class. + - **Modify imperative.ts? Yes** + +- packages\core\src\constants\Core.constants.ts + + - BaseProfile - This object contains a schema definition for a base profile. An authOrder property must be added to this object. + - **Modify BaseProfile ? Yes** + +- packages\imperative\src\config\src\ProfileInfo.ts + + - createSession - This function creates a session with key connection properties. Depending on the modification choices made for ConnectionPropsForSessCfg.resolveSessCfgProps, this function may have to call AuthOrder.putTopAuthInSession directly. + - **Modify createSession ? Yes** + +- packages\imperative\src\imperative\src\config\cmd\import\import.handler.ts + + - buildSession - This function was analyzed in the previous section of this document. + - **Modify buildSession ? No** + +- packages\imperative\src\rest\src\client\AbstractRestClient.ts + + - buildOptions - This function was analyzed in the previous section of this document. + - **Modify buildOptions ? No** + +- packages\imperative\src\rest\src\client\ProxySettings.ts + + - getProxyAgent - This function requires an ISession object. The ISession.authTypeOrder should have already been populated. Therefore, no change should be required in getProxyAgent + - **Modify getProxyAgent ? No** + +- packages\imperative\src\rest\src\client\doc\IHTTPSOptions.ts + + - IHTTPSOptions - This interface is only used by AbstractRestClient. Credentials are never extracted from nor added to this an instance of this interface. Therefore there is no reason to add authOrder to this interface. + - **Modify IHTTPSOptions ? No** + +- packages\imperative\src\rest\src\session\AbstractSession.ts + + - buildSession - This function was analyzed in the previous section of this document. + - **Modify buildSession ? Maybe** + +- packages\imperative\src\rest\src\session\doc\ISession.ts + + - authTypeOrder - This property was analyzed in the previous section of this document. + - **Modify authTypeOrder ? No** + +- packages\zosjobs\src\GetJobs.ts + + - getJob - This function displays rejectUnauthorized in a diagnostic message. No need to process authOrder here. + - **Modify getJob ? No** + +- packages\zosmf\src\ZosmfSession.ts + + - This Class contains option definitions for connection properties that can be defined in profiles. A definition of the authOrder connection property with a name like ZOSMF_OPTION_AUTH_ORDER should be added. + - **Modify ZosmfSession.ts ? Yes** + +- packages\zosmf\src\constants\Zosmf.messages.ts + + - This class provides message text used to display error details. There is no clear reason to add authOrder to this class. + - **Modify Zosmf.messages.ts ? No** + +- packages\zosmf\src\constants\Zosmf.profile.ts + + - ZosmfProfile - This class provides the property definitions for a zosmf profile type. We must add a definition for the authOrder property. + - **Modify Zosmf.profile.ts ? Yes** + +## \ No newline at end of file diff --git a/lerna.json b/lerna.json index 6c8ef4f5c6..1df9859d41 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "8.8.4", + "version": "8.10.1", "command": { "publish": { "ignoreChanges": [ diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index 91c5676cb2..62f8db5971 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -52,7 +52,7 @@ }, "__tests__/__packages__/cli-test-utils": { "name": "@zowe/cli-test-utils", - "version": "8.8.3", + "version": "8.10.1", "license": "EPL-2.0", "dependencies": { "find-up": "^5.0.0", @@ -63,7 +63,7 @@ "devDependencies": { "@types/js-yaml": "^4.0.9", "@types/uuid": "^10.0.0", - "@zowe/imperative": "8.8.3" + "@zowe/imperative": "8.10.1" }, "peerDependencies": { "@zowe/imperative": "^8.0.0" @@ -16269,21 +16269,21 @@ }, "packages/cli": { "name": "@zowe/cli", - "version": "8.8.4", + "version": "8.10.1", "hasInstallScript": true, "license": "EPL-2.0", "dependencies": { - "@zowe/core-for-zowe-sdk": "8.8.3", - "@zowe/imperative": "8.8.3", - "@zowe/provisioning-for-zowe-sdk": "8.8.3", - "@zowe/zos-console-for-zowe-sdk": "8.8.3", - "@zowe/zos-files-for-zowe-sdk": "8.8.4", - "@zowe/zos-jobs-for-zowe-sdk": "8.8.4", - "@zowe/zos-logs-for-zowe-sdk": "8.8.3", - "@zowe/zos-tso-for-zowe-sdk": "8.8.3", - "@zowe/zos-uss-for-zowe-sdk": "8.8.3", - "@zowe/zos-workflows-for-zowe-sdk": "8.8.4", - "@zowe/zosmf-for-zowe-sdk": "8.8.3", + "@zowe/core-for-zowe-sdk": "8.10.1", + "@zowe/imperative": "8.10.1", + "@zowe/provisioning-for-zowe-sdk": "8.10.1", + "@zowe/zos-console-for-zowe-sdk": "8.10.1", + "@zowe/zos-files-for-zowe-sdk": "8.10.1", + "@zowe/zos-jobs-for-zowe-sdk": "8.10.1", + "@zowe/zos-logs-for-zowe-sdk": "8.10.1", + "@zowe/zos-tso-for-zowe-sdk": "8.10.1", + "@zowe/zos-uss-for-zowe-sdk": "8.10.1", + "@zowe/zos-workflows-for-zowe-sdk": "8.10.1", + "@zowe/zosmf-for-zowe-sdk": "8.10.1", "find-process": "1.4.7", "lodash": "4.17.21", "minimatch": "9.0.5", @@ -16296,7 +16296,7 @@ "@types/diff": "^5.0.9", "@types/lodash": "^4.17.6", "@types/tar": "^6.1.11", - "@zowe/cli-test-utils": "8.8.3", + "@zowe/cli-test-utils": "8.10.1", "comment-json": "^4.2.3", "strip-ansi": "^6.0.1", "which": "^4.0.0" @@ -16352,15 +16352,15 @@ }, "packages/core": { "name": "@zowe/core-for-zowe-sdk", - "version": "8.8.3", + "version": "8.10.1", "license": "EPL-2.0", "dependencies": { "comment-json": "~4.2.3", "string-width": "^4.2.3" }, "devDependencies": { - "@zowe/cli-test-utils": "8.8.3", - "@zowe/imperative": "8.8.3" + "@zowe/cli-test-utils": "8.10.1", + "@zowe/imperative": "8.10.1" }, "engines": { "node": ">=18.12.0" @@ -16371,7 +16371,7 @@ }, "packages/imperative": { "name": "@zowe/imperative", - "version": "8.8.3", + "version": "8.10.1", "license": "EPL-2.0", "dependencies": { "@types/yargs": "^17.0.32", @@ -16565,16 +16565,16 @@ }, "packages/provisioning": { "name": "@zowe/provisioning-for-zowe-sdk", - "version": "8.8.3", + "version": "8.10.1", "license": "EPL-2.0", "dependencies": { "js-yaml": "^4.1.0" }, "devDependencies": { "@types/js-yaml": "^4.0.9", - "@zowe/cli-test-utils": "8.8.3", - "@zowe/core-for-zowe-sdk": "8.8.3", - "@zowe/imperative": "8.8.3" + "@zowe/cli-test-utils": "8.10.1", + "@zowe/core-for-zowe-sdk": "8.10.1", + "@zowe/imperative": "8.10.1" }, "engines": { "node": ">=18.12.0" @@ -16599,15 +16599,15 @@ }, "packages/workflows": { "name": "@zowe/zos-workflows-for-zowe-sdk", - "version": "8.8.4", + "version": "8.10.1", "license": "EPL-2.0", "dependencies": { - "@zowe/zos-files-for-zowe-sdk": "8.8.4" + "@zowe/zos-files-for-zowe-sdk": "8.10.1" }, "devDependencies": { - "@zowe/cli-test-utils": "8.8.3", - "@zowe/core-for-zowe-sdk": "8.8.3", - "@zowe/imperative": "8.8.3" + "@zowe/cli-test-utils": "8.10.1", + "@zowe/core-for-zowe-sdk": "8.10.1", + "@zowe/imperative": "8.10.1" }, "engines": { "node": ">=18.12.0" @@ -16619,12 +16619,12 @@ }, "packages/zosconsole": { "name": "@zowe/zos-console-for-zowe-sdk", - "version": "8.8.3", + "version": "8.10.1", "license": "EPL-2.0", "devDependencies": { - "@zowe/cli-test-utils": "8.8.3", - "@zowe/core-for-zowe-sdk": "8.8.3", - "@zowe/imperative": "8.8.3" + "@zowe/cli-test-utils": "8.10.1", + "@zowe/core-for-zowe-sdk": "8.10.1", + "@zowe/imperative": "8.10.1" }, "engines": { "node": ">=18.12.0" @@ -16636,17 +16636,17 @@ }, "packages/zosfiles": { "name": "@zowe/zos-files-for-zowe-sdk", - "version": "8.8.4", + "version": "8.10.1", "license": "EPL-2.0", "dependencies": { "lodash": "^4.17.21", "minimatch": "^9.0.5" }, "devDependencies": { - "@zowe/cli-test-utils": "8.8.3", - "@zowe/core-for-zowe-sdk": "8.8.3", - "@zowe/imperative": "8.8.3", - "@zowe/zos-uss-for-zowe-sdk": "8.8.3" + "@zowe/cli-test-utils": "8.10.1", + "@zowe/core-for-zowe-sdk": "8.10.1", + "@zowe/imperative": "8.10.1", + "@zowe/zos-uss-for-zowe-sdk": "8.10.1" }, "engines": { "node": ">=18.12.0" @@ -16678,15 +16678,15 @@ }, "packages/zosjobs": { "name": "@zowe/zos-jobs-for-zowe-sdk", - "version": "8.8.4", + "version": "8.10.1", "license": "EPL-2.0", "dependencies": { - "@zowe/zos-files-for-zowe-sdk": "8.8.4" + "@zowe/zos-files-for-zowe-sdk": "8.10.1" }, "devDependencies": { - "@zowe/cli-test-utils": "8.8.3", - "@zowe/core-for-zowe-sdk": "8.8.3", - "@zowe/imperative": "8.8.3" + "@zowe/cli-test-utils": "8.10.1", + "@zowe/core-for-zowe-sdk": "8.10.1", + "@zowe/imperative": "8.10.1" }, "engines": { "node": ">=18.12.0" @@ -16698,12 +16698,12 @@ }, "packages/zoslogs": { "name": "@zowe/zos-logs-for-zowe-sdk", - "version": "8.8.3", + "version": "8.10.1", "license": "EPL-2.0", "devDependencies": { - "@zowe/cli-test-utils": "8.8.3", - "@zowe/core-for-zowe-sdk": "8.8.3", - "@zowe/imperative": "8.8.3" + "@zowe/cli-test-utils": "8.10.1", + "@zowe/core-for-zowe-sdk": "8.10.1", + "@zowe/imperative": "8.10.1" }, "engines": { "node": ">=18.12.0" @@ -16715,12 +16715,12 @@ }, "packages/zosmf": { "name": "@zowe/zosmf-for-zowe-sdk", - "version": "8.8.3", + "version": "8.10.1", "license": "EPL-2.0", "devDependencies": { - "@zowe/cli-test-utils": "8.8.3", - "@zowe/core-for-zowe-sdk": "8.8.3", - "@zowe/imperative": "8.8.3" + "@zowe/cli-test-utils": "8.10.1", + "@zowe/core-for-zowe-sdk": "8.10.1", + "@zowe/imperative": "8.10.1" }, "engines": { "node": ">=18.12.0" @@ -16732,15 +16732,15 @@ }, "packages/zostso": { "name": "@zowe/zos-tso-for-zowe-sdk", - "version": "8.8.3", + "version": "8.10.1", "license": "EPL-2.0", "dependencies": { - "@zowe/zosmf-for-zowe-sdk": "8.8.3" + "@zowe/zosmf-for-zowe-sdk": "8.10.1" }, "devDependencies": { - "@zowe/cli-test-utils": "8.8.3", - "@zowe/core-for-zowe-sdk": "8.8.3", - "@zowe/imperative": "8.8.3" + "@zowe/cli-test-utils": "8.10.1", + "@zowe/core-for-zowe-sdk": "8.10.1", + "@zowe/imperative": "8.10.1" }, "engines": { "node": ">=18.12.0" @@ -16752,15 +16752,15 @@ }, "packages/zosuss": { "name": "@zowe/zos-uss-for-zowe-sdk", - "version": "8.8.3", + "version": "8.10.1", "license": "EPL-2.0", "dependencies": { "ssh2": "^1.15.0" }, "devDependencies": { "@types/ssh2": "^1.11.19", - "@zowe/cli-test-utils": "8.8.3", - "@zowe/imperative": "8.8.3" + "@zowe/cli-test-utils": "8.10.1", + "@zowe/imperative": "8.10.1" }, "engines": { "node": ">=18.12.0" diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index f98bd7f182..0533d487e2 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,10 +1,10 @@ # Change Log All notable changes to the Zowe CLI package will be documented in this file. -## Recent Changes +## `8.10.0` -Enhancement: The `zowe zos-files copy data-set` command now copies members from a source partitioned data set to an existing target partitioned data set.[#2386](https://github.com/zowe/zowe-cli/pull/2386) -## Recent Changes +## `8.9.0` - Enhancement: Added new command zowe zos-files download all-members-matching, (zowe files dl amm), to download members matching specified pattern(s). The success message for the Download.allMembers API was changed from originally "Data set downloaded successfully" to "Member(s) downloaded successfully." The change also alters the commandResponse when using the --rfj flag. [#2359](https://github.com/zowe/zowe-cli/pull/2359) ## `8.8.0` diff --git a/packages/cli/package.json b/packages/cli/package.json index e53c6044f0..cf52e04915 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/cli", - "version": "8.8.4", + "version": "8.10.1", "zoweVersion": "v3.0.0", "description": "Zowe CLI is a command line interface (CLI) that provides a simple and streamlined way to interact with IBM z/OS.", "author": "Zowe", @@ -58,17 +58,17 @@ "preshrinkwrap": "node ../../scripts/rewriteShrinkwrap.js" }, "dependencies": { - "@zowe/core-for-zowe-sdk": "8.8.3", - "@zowe/imperative": "8.8.3", - "@zowe/provisioning-for-zowe-sdk": "8.8.3", - "@zowe/zos-console-for-zowe-sdk": "8.8.3", - "@zowe/zos-files-for-zowe-sdk": "8.8.4", - "@zowe/zos-jobs-for-zowe-sdk": "8.8.4", - "@zowe/zos-logs-for-zowe-sdk": "8.8.3", - "@zowe/zos-tso-for-zowe-sdk": "8.8.3", - "@zowe/zos-uss-for-zowe-sdk": "8.8.3", - "@zowe/zos-workflows-for-zowe-sdk": "8.8.4", - "@zowe/zosmf-for-zowe-sdk": "8.8.3", + "@zowe/core-for-zowe-sdk": "8.10.1", + "@zowe/imperative": "8.10.1", + "@zowe/provisioning-for-zowe-sdk": "8.10.1", + "@zowe/zos-console-for-zowe-sdk": "8.10.1", + "@zowe/zos-files-for-zowe-sdk": "8.10.1", + "@zowe/zos-jobs-for-zowe-sdk": "8.10.1", + "@zowe/zos-logs-for-zowe-sdk": "8.10.1", + "@zowe/zos-tso-for-zowe-sdk": "8.10.1", + "@zowe/zos-uss-for-zowe-sdk": "8.10.1", + "@zowe/zos-workflows-for-zowe-sdk": "8.10.1", + "@zowe/zosmf-for-zowe-sdk": "8.10.1", "find-process": "1.4.7", "lodash": "4.17.21", "minimatch": "9.0.5", @@ -78,7 +78,7 @@ "@types/diff": "^5.0.9", "@types/lodash": "^4.17.6", "@types/tar": "^6.1.11", - "@zowe/cli-test-utils": "8.8.3", + "@zowe/cli-test-utils": "8.10.1", "comment-json": "^4.2.3", "strip-ansi": "^6.0.1", "which": "^4.0.0" diff --git a/packages/core/package.json b/packages/core/package.json index abc273e430..91a8be1c76 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/core-for-zowe-sdk", - "version": "8.8.3", + "version": "8.10.1", "description": "Core libraries shared by Zowe SDK packages", "author": "Zowe", "license": "EPL-2.0", @@ -49,8 +49,8 @@ "string-width": "^4.2.3" }, "devDependencies": { - "@zowe/cli-test-utils": "8.8.3", - "@zowe/imperative": "8.8.3" + "@zowe/cli-test-utils": "8.10.1", + "@zowe/imperative": "8.10.1" }, "peerDependencies": { "@zowe/imperative": "^8.0.0" diff --git a/packages/imperative/CHANGELOG.md b/packages/imperative/CHANGELOG.md index ded21b7c84..0ba57c37bf 100644 --- a/packages/imperative/CHANGELOG.md +++ b/packages/imperative/CHANGELOG.md @@ -2,6 +2,16 @@ All notable changes to the Imperative package will be documented in this file. + +## `8.10.1` + +- BugFix: Resolved an issue where base profiles in a team configuration file were overwritten when a user configuration file did not include a base profile. [#2383](https://github.com/zowe/zowe-cli/pull/2383) + +## `8.10.0` + +- BugFix: Modified location of Proxy-Authorization header to be located in the agent instead of the request. [#2389](https://github.com/zowe/zowe-cli/issues/2389) + + ## `8.8.3` - BugFix: Modified 8.8.2 bugfix to correct web help alias. [#2361](https://github.com/zowe/zowe-cli/pull/2361) diff --git a/packages/imperative/package.json b/packages/imperative/package.json index 6b197330b6..1f79f56d67 100644 --- a/packages/imperative/package.json +++ b/packages/imperative/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/imperative", - "version": "8.8.3", + "version": "8.10.1", "description": "framework for building configurable CLIs", "author": "Zowe", "license": "EPL-2.0", diff --git a/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts b/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts index d4f2aeaf6f..8732ef1399 100644 --- a/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts +++ b/packages/imperative/src/config/__tests__/ProfileInfo.TeamConfig.unit.test.ts @@ -677,7 +677,14 @@ describe("TeamConfig ProfileInfo tests", () => { expect(arg.argValue).toEqual(expectedArgs[idx].argValue); expect(arg.argLoc.locType).toBe(ProfLocType.TEAM_CONFIG); expect(arg.argLoc.jsonLoc).toMatch(/^profiles\.(base_glob|LPAR2_home)\.properties\./); - expect(arg.argLoc.osLoc[0]).toEqual(path.normalize(path.join(teamHomeProjDir, `${testAppNm}.config.json`))); + expect([ + path.normalize( + path.join(teamHomeProjDir, `${testAppNm}.config.json`) + ), + path.normalize( + path.join(teamProjDir, `${testAppNm}.config.json`) + ), + ]).toContain(arg.argLoc.osLoc[0]); } }); @@ -956,6 +963,44 @@ describe("TeamConfig ProfileInfo tests", () => { expect(caughtError).toBeDefined(); expect(caughtError.message).toContain("Profile attributes must be defined"); }); + it("should fall back to layerProperties when realBaseProfileName is undefined", async () => { + const profInfo = createNewProfInfo(teamProjDir); + await profInfo.readProfilesFromDisk(); + + jest.spyOn(ProfileInfo.prototype, "getOsLocInfo").mockReturnValue([{ + global: true, + name: "LPAR1", + path: "/mocked/path/xyz", + user: true + }]); + + // Simulate the condition where the active layer has no `defaults.base` but global and user layers exist + const layerActive = profInfo.getTeamConfig().layerActive(); + delete layerActive.properties.defaults.base; + + const globalLayer = profInfo.getTeamConfig().findLayer(false, true); + globalLayer.properties.defaults.base = "globalBaseProfile"; + + const userLayer = profInfo.getTeamConfig().findLayer(true, true); + userLayer.properties.defaults.base = ""; + + const profAttrs = profInfo.getDefaultProfile("zosmf") as IProfAttrs; + + // Merge args to trigger the logic + const mergedArgs = profInfo.mergeArgsForProfile(profAttrs); + + // Expected args should include those from the global base profile + const expectedArgs = [ + {argName: 'host', dataType: 'string', argValue: 'LPAR1.your.domain.net', secure: false}, + {argName: 'port', dataType: 'number', argValue: 1234, secure: false}, + {argName: 'responseFormatHeader', dataType: 'boolean', argValue: true, secure: false} + ]; + + expect(mergedArgs.knownArgs.length).toBeGreaterThanOrEqual(expectedArgs.length); + for (const [idx, arg] of expectedArgs.entries()) { + expect(mergedArgs.knownArgs[idx]).toMatchObject(arg); + } + }); }); describe("mergeArgsForProfileType", () => { diff --git a/packages/imperative/src/config/src/ProfileInfo.ts b/packages/imperative/src/config/src/ProfileInfo.ts index 27119a92b9..978a34cd9b 100644 --- a/packages/imperative/src/config/src/ProfileInfo.ts +++ b/packages/imperative/src/config/src/ProfileInfo.ts @@ -597,6 +597,10 @@ export class ProfileInfo { if (osLoc?.global) { layerProperties = this.mLoadedConfig.findLayer(osLoc.user, osLoc.global)?.properties; realBaseProfileName = layerProperties?.defaults.base; + if (!realBaseProfileName && osLoc.user) { + layerProperties = this.mLoadedConfig.findLayer(false, osLoc.global)?.properties; + realBaseProfileName = layerProperties?.defaults.base; + } if (realBaseProfileName) baseProfile = this.mLoadedConfig.api.profiles.buildProfile(realBaseProfileName, layerProperties?.profiles); else baseProfile = null; } @@ -1650,15 +1654,11 @@ export class ProfileInfo { }; let filePath: string; - if (_isPropInLayer(opts.configProperties) && opts.osLocInfo) { - filePath = opts.osLocInfo.path; - } else { - for (const layer of this.mLoadedConfig.mLayers) { - // Find the first layer that includes the JSON path - if (_isPropInLayer(layer.properties)) { - filePath = layer.path; - break; - } + for (const layer of this.mLoadedConfig.mLayers) { + // Find the first layer that includes the JSON path + if (_isPropInLayer(layer.properties)) { + filePath = layer.path; + break; } } diff --git a/packages/imperative/src/rest/__tests__/client/AbstractRestClient.unit.test.ts b/packages/imperative/src/rest/__tests__/client/AbstractRestClient.unit.test.ts index fcd7669552..98005ccff5 100644 --- a/packages/imperative/src/rest/__tests__/client/AbstractRestClient.unit.test.ts +++ b/packages/imperative/src/rest/__tests__/client/AbstractRestClient.unit.test.ts @@ -1502,22 +1502,6 @@ describe("AbstractRestClient tests", () => { const result = privateRestClient.buildOptions(resource, request, reqHeaders); expect(Object.keys(result)).toContain('agent'); }); - - it('Should use session proxy options over env vars for proxy agent', () => { - restSession.ISession.proxy = { proxy_authorization: 'proxy_auth_string'}; - const resource = '/resource'; - const request = ''; - const reqHeaders: any[] = []; - const url = new URL('https://www.zowe.com'); - const proxyAgent = new HttpsProxyAgent(url, { rejectUnauthorized: true }); - getSystemProxyUrlSpy.mockReturnValue(url); - getProxyAgentSpy.mockReturnValue(proxyAgent); - setCertPemAuthSpy.mockReturnValue(true); - const headerSpy = jest.spyOn(privateRestClient, "appendHeaders"); - const result = privateRestClient.buildOptions(resource, request, reqHeaders); - expect(Object.keys(result)).toContain('agent'); - expect(headerSpy).toHaveBeenCalledWith([{'Proxy-Authorization': restSession.ISession.proxy.proxy_authorization}]); - }); }); }); }); diff --git a/packages/imperative/src/rest/__tests__/client/ProxySettings.unit.test.ts b/packages/imperative/src/rest/__tests__/client/ProxySettings.unit.test.ts index 952b9e298f..f6ff5aac4c 100644 --- a/packages/imperative/src/rest/__tests__/client/ProxySettings.unit.test.ts +++ b/packages/imperative/src/rest/__tests__/client/ProxySettings.unit.test.ts @@ -23,7 +23,7 @@ describe("Proxy tests", () => { const session = { hostname: "fake.com", port: 443, - rejectUnauthorized: false + rejectUnauthorized: false, } as ISession; const privateProxy = ProxySettings as any; const httpUrl = "http://www.zowe.com"; @@ -31,7 +31,9 @@ describe("Proxy tests", () => { const noProxyList = "www.zowe.com, fake.com,ibm.com,broadcom.com "; const passedUrl = "passedurl.com"; let getProxySettingsSpy: jest.SpyInstance; + let getProxyAuthSettingSpy: jest.SpyInstance; let checkUrlSpy: jest.SpyInstance; + let matchesNoProxySettingsSpy: jest.SpyInstance; describe("recognise passed proxy values in session", () => { const noProxySpy = jest.spyOn(privateProxy, "matchesNoProxySettings"); @@ -40,7 +42,7 @@ describe("Proxy tests", () => { checkUrlSpy = jest.spyOn(privateProxy, "checkUrl"); const expected = { proxyUrl: passedUrl, - protocol: HTTPS_PROTOCOL + protocol: HTTPS_PROTOCOL, }; beforeEach(() => { @@ -55,7 +57,9 @@ describe("Proxy tests", () => { expect(httpEnvVarSpy).not.toHaveBeenCalled(); expect(httpsEnvVarSpy).not.toHaveBeenCalled(); checkUrlSpy.mockReturnValueOnce(passedUrl); - expect(JSON.stringify(ProxySettings["getProxySettings"](session))).toEqual(JSON.stringify(expected)); + expect( + JSON.stringify(ProxySettings["getProxySettings"](session)) + ).toEqual(JSON.stringify(expected)); noProxySpy.mockClear(); checkUrlSpy.mockClear(); }); @@ -67,34 +71,89 @@ describe("Proxy tests", () => { expect(httpEnvVarSpy).not.toHaveBeenCalled(); expect(httpsEnvVarSpy).not.toHaveBeenCalled(); checkUrlSpy.mockReturnValueOnce(passedUrl); - expect(JSON.stringify(ProxySettings["getProxySettings"](session))).toEqual(JSON.stringify(expected)); + expect( + JSON.stringify(ProxySettings["getProxySettings"](session)) + ).toEqual(JSON.stringify(expected)); noProxySpy.mockClear(); checkUrlSpy.mockClear(); }); }); describe("getProxyAgent", () => { + const headers = { + "Proxy-Authorization": "Basic ==ThisIsATest123", + }; + beforeEach(() => { jest.clearAllMocks(); + jest.restoreAllMocks(); + jest.resetModules(); + jest.resetAllMocks(); getProxySettingsSpy = jest.spyOn(privateProxy, "getProxySettings"); + getProxyAuthSettingSpy = jest.spyOn( + privateProxy, + "getProxyAuthHeader" + ); }); it("Should retrieve the HTTP proxy agent", () => { - const expected = new HttpProxyAgent(httpUrl); + const expected = new HttpProxyAgent(httpUrl, { headers }); getProxySettingsSpy.mockReturnValue({ proxyUrl: httpUrl, - protocol: HTTP_PROTOCOL + protocol: HTTP_PROTOCOL, }); - expect(JSON.stringify(ProxySettings.getProxyAgent(session))).toEqual(JSON.stringify(expected)); + getProxyAuthSettingSpy.mockReturnValue(headers); + expect( + JSON.stringify(ProxySettings.getProxyAgent(session)) + ).toEqual(JSON.stringify(expected)); }); it("Should retrieve the HTTPS proxy agent", () => { - const expected = new HttpsProxyAgent(httpsUrl, { rejectUnauthorized: false }); + const expected = new HttpsProxyAgent(httpsUrl, { + rejectUnauthorized: false, + }); getProxySettingsSpy.mockReturnValue({ proxyUrl: httpsUrl, - protocol: HTTPS_PROTOCOL + protocol: HTTPS_PROTOCOL, }); - expect(JSON.stringify(ProxySettings.getProxyAgent(session))).toEqual(JSON.stringify(expected)); + expect( + JSON.stringify(ProxySettings.getProxyAgent(session)) + ).toEqual(JSON.stringify(expected)); + }); + + it("Should return undefined when a protocol is not defined in the session", () => { + const noProtocolSession = { ...session }; + noProtocolSession.protocol = undefined; + expect(ProxySettings.getProxyAgent(session)).toEqual(undefined); + }); + }); + + describe("getProxyAuthHeader", () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + jest.resetModules(); + jest.resetAllMocks(); + }); + + it("Should retrieve the auth header from the proxy settings", () => { + const proxyAuthSetting = "Basic ==ThisIsATest123"; + expect( + ProxySettings["getProxyAuthHeader"]({ + authSetting: proxyAuthSetting, + proxyUrl: new URL("https://www.google.com/"), + protocol: HTTPS_PROTOCOL, + }) + ).toEqual({ "Proxy-Authorization": proxyAuthSetting }); + }); + + it("Should return undefined if the proxy auth setting is not in the proxy settings", () => { + expect( + ProxySettings["getProxyAuthHeader"]({ + proxyUrl: new URL("https://www.google.com/"), + protocol: HTTPS_PROTOCOL, + }) + ).toEqual(undefined); }); }); @@ -107,7 +166,7 @@ describe("Proxy tests", () => { it("Should retrieve the system proxy URL", () => { getProxySettingsSpy.mockReturnValue({ proxyUrl: httpsUrl, - protocol: HTTPS_PROTOCOL + protocol: HTTPS_PROTOCOL, }); expect(ProxySettings.getSystemProxyUrl(session)).toEqual(httpsUrl); }); @@ -116,16 +175,36 @@ describe("Proxy tests", () => { describe("getProxySettings", () => { beforeEach(() => { jest.clearAllMocks(); + jest.restoreAllMocks(); + jest.resetModules(); + jest.resetAllMocks(); checkUrlSpy = jest.spyOn(privateProxy, "checkUrl"); + matchesNoProxySettingsSpy = jest.spyOn( + privateProxy, + "matchesNoProxySettings" + ); }); it("Should return proxy settings from session", () => { const expected = { proxyUrl: httpsUrl, - protocol: HTTPS_PROTOCOL + protocol: HTTPS_PROTOCOL, + authSetting: "Basic ==ThisIsATest123", }; checkUrlSpy.mockReturnValue(httpsUrl); - expect(ProxySettings["getProxySettings"](session)).toEqual(expected); + session.proxy = { + proxy_authorization: "Basic ==ThisIsATest123", + }; + expect(ProxySettings["getProxySettings"](session)).toEqual( + expected + ); + }); + + it("Should return undefined proxy url matchesNoProxySettings", () => { + matchesNoProxySettingsSpy.mockReturnValue(true); + expect(ProxySettings["getProxySettings"](session)).toEqual( + undefined + ); }); }); @@ -143,28 +222,46 @@ describe("Proxy tests", () => { }); describe("matchesNoProxySettings", () => { - it("Should match session hostname with no_proxy", () => { + beforeEach(() => { + jest.clearAllMocks(); + jest.restoreAllMocks(); + jest.resetModules(); + jest.resetAllMocks(); + }); + + it("Should match session hostname with no_proxy", () => { const expected = true; process.env["NO_PROXY"] = noProxyList; - expect(ProxySettings["matchesNoProxySettings"](session)).toEqual(expected); + expect(ProxySettings["matchesNoProxySettings"](session)).toEqual( + expected + ); process.env["NO_PROXY"] = undefined; }); it("Should return true for match with no_proxy passed with session proxy", () => { session.proxy = { http_proxy: passedUrl, no_proxy: ["fake.com"] }; session.protocol = HTTP_PROTOCOL; - expect(ProxySettings["matchesNoProxySettings"](session)).toEqual(true); + expect(ProxySettings["matchesNoProxySettings"](session)).toEqual( + true + ); }); - it("Should not match session hostname with no_proxy", () => { + it("Should not match session hostname with no_proxy", () => { const expected = false; process.env["NO_PROXY"] = noProxyList; session.hostname = "microsoft.com"; - expect(ProxySettings["matchesNoProxySettings"](session)).toEqual(expected); + expect(ProxySettings["matchesNoProxySettings"](session)).toEqual( + expected + ); process.env["NO_PROXY"] = undefined; }); it("Should return false for match with no_proxy passed with session proxy", () => { - session.proxy = { http_proxy: passedUrl, no_proxy: ["false.com", "blah.com"] }; + session.proxy = { + http_proxy: passedUrl, + no_proxy: ["false.com", "blah.com"], + }; session.protocol = HTTP_PROTOCOL; - expect(ProxySettings["matchesNoProxySettings"](session)).toEqual(false); + expect(ProxySettings["matchesNoProxySettings"](session)).toEqual( + false + ); }); }); }); diff --git a/packages/imperative/src/rest/src/client/AbstractRestClient.ts b/packages/imperative/src/rest/src/client/AbstractRestClient.ts index 9da363a5cd..c4f908709c 100644 --- a/packages/imperative/src/rest/src/client/AbstractRestClient.ts +++ b/packages/imperative/src/rest/src/client/AbstractRestClient.ts @@ -476,9 +476,6 @@ export abstract class AbstractRestClient { this.mLogger.info(`Proxy setting "${proxyUrl.href}" will not be used as hostname was found listed under "no_proxy" setting.`); } else { this.mLogger.info(`Using the following proxy setting for the request: ${proxyUrl.href}`); - if (this.session.ISession.proxy?.proxy_authorization) { - reqHeaders.push({ 'Proxy-Authorization': this.session.ISession.proxy.proxy_authorization}); - } options.agent = ProxySettings.getProxyAgent(this.session.ISession); } } diff --git a/packages/imperative/src/rest/src/client/ProxySettings.ts b/packages/imperative/src/rest/src/client/ProxySettings.ts index c414003a64..56b8438d3f 100644 --- a/packages/imperative/src/rest/src/client/ProxySettings.ts +++ b/packages/imperative/src/rest/src/client/ProxySettings.ts @@ -9,14 +9,18 @@ * */ -import { env } from 'process'; -import { URL } from 'url'; -import { Agent } from 'https'; -import { HttpProxyAgent } from 'http-proxy-agent'; -import { HttpsProxyAgent } from 'https-proxy-agent'; +import { env } from "process"; +import { URL } from "url"; +import { Agent } from "https"; +import { HttpProxyAgent } from "http-proxy-agent"; +import { HttpsProxyAgent } from "https-proxy-agent"; -import { HTTP_PROTOCOL_CHOICES, HTTP_PROTOCOL, HTTPS_PROTOCOL } from '../session/SessConstants'; -import { ISession } from '../session/doc/ISession'; +import { + HTTP_PROTOCOL_CHOICES, + HTTP_PROTOCOL, + HTTPS_PROTOCOL, +} from "../session/SessConstants"; +import { ISession } from "../session/doc/ISession"; /** * Utility class to provide an http agent to REST APIs that is configured for @@ -33,7 +37,6 @@ import { ISession } from '../session/doc/ISession'; * to match with the hostname of the Zowe profile. */ export class ProxySettings { - /** * Retrieve an appropriate http.agent instance if proxy environment variables can be found. * @static @@ -45,12 +48,21 @@ export class ProxySettings { */ public static getProxyAgent(session: ISession): Agent | undefined { const proxySetting = this.getProxySettings(session); - if (proxySetting?.protocol === HTTP_PROTOCOL) { - return new HttpProxyAgent(proxySetting.proxyUrl); + const proxyOptions = {} as ProxyOptions; + const authHeader = ProxySettings.getProxyAuthHeader(proxySetting); + if (authHeader) { + proxyOptions.headers = authHeader; + } + if (!proxySetting?.protocol) { + return; } - if (proxySetting?.protocol === HTTPS_PROTOCOL) { - return new HttpsProxyAgent(proxySetting.proxyUrl, - { rejectUnauthorized: session.rejectUnauthorized ?? true }); + if (proxySetting.protocol === HTTP_PROTOCOL) { + return new HttpProxyAgent(proxySetting.proxyUrl, proxyOptions); + } + if (proxySetting.protocol === HTTPS_PROTOCOL) { + proxyOptions.rejectUnauthorized = + session.rejectUnauthorized ?? true; + return new HttpsProxyAgent(proxySetting.proxyUrl, proxyOptions); } } @@ -78,7 +90,8 @@ export class ProxySettings { * @memberof ProxySettings */ public static matchesNoProxySettings(session: ISession): boolean { - const noProxyValues = session.proxy?.no_proxy ?? this.getNoProxyEnvVariables(); + const noProxyValues = + session.proxy?.no_proxy ?? this.getNoProxyEnvVariables(); if (!noProxyValues) { return false; } @@ -88,6 +101,14 @@ export class ProxySettings { return false; } + private static getProxyAuthHeader( + proxySetting: ProxySetting + ): { [key: string]: string } | undefined { + return proxySetting?.authSetting + ? { "Proxy-Authorization": proxySetting.authSetting } + : undefined; + } + /** * Parses environment variables for proxy servers. * @private @@ -96,21 +117,29 @@ export class ProxySettings { * @returns instance of private `ProxySetting` or `undefined` * @memberof ProxySettings */ - private static getProxySettings(session: ISession): ProxySetting | undefined { + private static getProxySettings( + session: ISession + ): ProxySetting | undefined { if (this.matchesNoProxySettings(session)) { return; } const protocol = session.protocol ?? HTTPS_PROTOCOL; let envVariable: string | undefined; if (protocol === HTTP_PROTOCOL) { - envVariable = session.proxy?.http_proxy ?? this.getHttpEnvVariables(); - } - else if (protocol === HTTPS_PROTOCOL) { - envVariable = session.proxy?.https_proxy ?? this.getHttpsEnvVariables(); + envVariable = + session.proxy?.http_proxy ?? this.getHttpEnvVariables(); + } else if (protocol === HTTPS_PROTOCOL) { + envVariable = + session.proxy?.https_proxy ?? this.getHttpsEnvVariables(); } const proxyUrl = this.checkUrl(envVariable); + + const authSetting = session.proxy?.proxy_authorization; + if (authSetting) { + return { proxyUrl, protocol, authSetting }; + } if (proxyUrl) { - return {proxyUrl, protocol}; + return { proxyUrl, protocol }; } } @@ -149,7 +178,9 @@ export class ProxySettings { if (!noProxyValue) { return; } - return noProxyValue.split(',').map(entry => entry.trim().toLocaleLowerCase()); + return noProxyValue + .split(",") + .map((entry) => entry.trim().toLocaleLowerCase()); } /** @@ -173,6 +204,12 @@ export class ProxySettings { * Internal interface to group proxy settings */ interface ProxySetting { - proxyUrl: URL, - protocol: HTTP_PROTOCOL_CHOICES + proxyUrl: URL; + protocol: HTTP_PROTOCOL_CHOICES; + authSetting?: string; +} + +interface ProxyOptions { + headers?: { [key: string]: string }; + rejectUnauthorized?: boolean; } diff --git a/packages/provisioning/package.json b/packages/provisioning/package.json index 74ba89c9c2..d73cca706d 100644 --- a/packages/provisioning/package.json +++ b/packages/provisioning/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/provisioning-for-zowe-sdk", - "version": "8.8.3", + "version": "8.10.1", "description": "Zowe SDK to interact with the z/OS provisioning APIs", "author": "Zowe", "license": "EPL-2.0", @@ -49,9 +49,9 @@ }, "devDependencies": { "@types/js-yaml": "^4.0.9", - "@zowe/cli-test-utils": "8.8.3", - "@zowe/core-for-zowe-sdk": "8.8.3", - "@zowe/imperative": "8.8.3" + "@zowe/cli-test-utils": "8.10.1", + "@zowe/core-for-zowe-sdk": "8.10.1", + "@zowe/imperative": "8.10.1" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0", diff --git a/packages/workflows/package.json b/packages/workflows/package.json index e9cf5160de..83e62ab6ef 100644 --- a/packages/workflows/package.json +++ b/packages/workflows/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-workflows-for-zowe-sdk", - "version": "8.8.4", + "version": "8.10.1", "description": "Zowe SDK to interact with the z/OS workflows APIs", "author": "Zowe", "license": "EPL-2.0", @@ -45,12 +45,12 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "dependencies": { - "@zowe/zos-files-for-zowe-sdk": "8.8.4" + "@zowe/zos-files-for-zowe-sdk": "8.10.1" }, "devDependencies": { - "@zowe/cli-test-utils": "8.8.3", - "@zowe/core-for-zowe-sdk": "8.8.3", - "@zowe/imperative": "8.8.3" + "@zowe/cli-test-utils": "8.10.1", + "@zowe/core-for-zowe-sdk": "8.10.1", + "@zowe/imperative": "8.10.1" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0", diff --git a/packages/zosconsole/package.json b/packages/zosconsole/package.json index cd8be18e9c..a8bb679910 100644 --- a/packages/zosconsole/package.json +++ b/packages/zosconsole/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-console-for-zowe-sdk", - "version": "8.8.3", + "version": "8.10.1", "description": "Zowe SDK to interact with the z/OS console", "author": "Zowe", "license": "EPL-2.0", @@ -45,9 +45,9 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "devDependencies": { - "@zowe/cli-test-utils": "8.8.3", - "@zowe/core-for-zowe-sdk": "8.8.3", - "@zowe/imperative": "8.8.3" + "@zowe/cli-test-utils": "8.10.1", + "@zowe/core-for-zowe-sdk": "8.10.1", + "@zowe/imperative": "8.10.1" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0", diff --git a/packages/zosfiles/CHANGELOG.md b/packages/zosfiles/CHANGELOG.md index 1a9d5f993f..568ada539e 100644 --- a/packages/zosfiles/CHANGELOG.md +++ b/packages/zosfiles/CHANGELOG.md @@ -8,7 +8,15 @@ All notable changes to the Zowe z/OS files SDK package will be documented in thi ## Recent Changes - Enhancement: The `Copy.dataset` method now recognizes partioned data sets and can copy members of a source PDS into an existing target PDS. [#2386](https://github.com/zowe/zowe-cli/pull/2386) -## Recent Changes +## `8.10.0` +- Enhancement: The `Copy.dataset` method now recognizes partitioned data sets and can copy members of a source PDS into an existing target PDS. [#2386](https://github.com/zowe/zowe-cli/pull/2386) + +## `8.9.1` + +- BugFix: Corrected the `apiResponse` response value from `streamToDataSet()`,`streamToUss()`,`bufferToUss()` and `bufferToDataSet()` on the Upload SDK. [#2381](https://github.com/zowe/zowe-cli/pull/2381) +- BugFix: Corrected the `Upload.BufferToUssFile()` SDK function to properly tag uploaded files. [#2378](https://github.com/zowe/zowe-cli/pull/2378) + +## `8.9.0` - Enhancement: Added a `List.membersMatchingPattern` method to download all members that match a specific pattern.[#2359](https://github.com/zowe/zowe-cli/pull/2359) ## `8.8.4` diff --git a/packages/zosfiles/__tests__/__system__/methods/upload/Upload.system.test.ts b/packages/zosfiles/__tests__/__system__/methods/upload/Upload.system.test.ts index de22e9569e..b93d74db4c 100644 --- a/packages/zosfiles/__tests__/__system__/methods/upload/Upload.system.test.ts +++ b/packages/zosfiles/__tests__/__system__/methods/upload/Upload.system.test.ts @@ -19,6 +19,7 @@ import { deleteFiles, getUniqueDatasetName, stripNewLines, wait, waitTime } from import * as fs from "fs"; import { ITestEnvironment } from "../../../../../../__tests__/__src__/environment/ITestEnvironment"; import { runCliScript } from "../../../../../../__tests__/__packages__/cli-test-utils/src"; +import { Readable } from "stream"; let REAL_SESSION: Session; let testEnvironment: ITestEnvironment; @@ -309,6 +310,53 @@ describe("Upload Data Set", () => { } }); + it("should upload a PDS file - bufferToDataSet()", async () => { + let error; + let uploadResponse; + let getResponse; + const data: Buffer = Buffer.from(testdata); + + try { + uploadResponse = await Upload.bufferToDataSet(REAL_SESSION, data, dsname+"(TEST)"); + getResponse = await Get.dataSet(REAL_SESSION, dsname+"(TEST)"); + } catch (err) { + error = err; + Imperative.console.info("Error: " + inspect(error)); + } + + expect(error).toBeFalsy(); + + expect(uploadResponse.apiResponse).toMatchObject({ + success: true, + from: "", + to: dsname + "(TEST)", + }); + expect(Buffer.from(getResponse.toString().trim())).toEqual(data); + }); + + it("should upload a PDS file - streamToDataSet()", async () => { + let error; + let uploadResponse; + let getResponse; + + const inputStream = new Readable(); + inputStream.push(testdata); + inputStream.push(null); + + try { + uploadResponse = await Upload.streamToDataSet(REAL_SESSION, inputStream, dsname+"(TEST)"); + getResponse = await Get.dataSet(REAL_SESSION, dsname+"(TEST)"); + } catch (err) { + error = err; + Imperative.console.info("Error: " + inspect(error)); + } + + expect(error).toBeFalsy(); + + expect(uploadResponse.apiResponse).toMatchObject({"success": true, "from": "[Readable]","to": dsname+"(TEST)"}); + expect(getResponse.toString().trim()).toEqual(testdata); + }); + it("should upload a file to a partitioned data set member using full path", async () => { let error; let response: IZosFilesResponse; @@ -733,7 +781,7 @@ describe("Upload USS file", () => { await deleteFiles(REAL_SESSION, ussname); }); - it("should upload a USS file", async () => { + it("should upload a USS file - bufferToUssFile()", async () => { let error; let uploadResponse; let getResponse; @@ -748,18 +796,45 @@ describe("Upload USS file", () => { } expect(error).toBeFalsy(); + + expect(uploadResponse.apiResponse).toMatchObject({"success": true, "from": "","to": ussname}); expect(getResponse).toEqual(Buffer.from(data.toString())); + }); + + it("should upload a USS file - streamToUssFile()", async () => { + let error; + let uploadResponse; + let getResponse; + const inputStream = new Readable(); + inputStream.push(testdata); + inputStream.push(null); + + try { + uploadResponse = await Upload.streamToUssFile(REAL_SESSION, ussname, inputStream); + getResponse = await Get.USSFile(REAL_SESSION, ussname); + } catch (err) { + error = err; + Imperative.console.info("Error: " + inspect(error)); + } + expect(error).toBeFalsy(); + + expect(uploadResponse.apiResponse).toMatchObject({"success": true, "from": "[Readable]","to": ussname}); + expect(getResponse).toEqual(Buffer.from(testdata)); }); + it("should upload a USS file in binary mode", async () => { let error; let uploadResponse; let getResponse; + let tagResponse; + const data: Buffer = Buffer.from(testdata); try { uploadResponse = await Upload.bufferToUssFile(REAL_SESSION, ussname, data, { binary: true }); getResponse = await Get.USSFile(REAL_SESSION, ussname, {binary: true}); + tagResponse = await Utilities.isFileTagBinOrAscii(REAL_SESSION, ussname); } catch (err) { error = err; Imperative.console.info("Error: " + inspect(error)); @@ -767,7 +842,7 @@ describe("Upload USS file", () => { expect(error).toBeFalsy(); expect(getResponse).toEqual(Buffer.from(data.toString())); - + expect(tagResponse).toBe(true); }); it("should upload a USS file from local file", async () => { let error; diff --git a/packages/zosfiles/__tests__/__unit__/methods/upload/Upload.unit.test.ts b/packages/zosfiles/__tests__/__unit__/methods/upload/Upload.unit.test.ts index 7e7e8bf363..3e758ae690 100644 --- a/packages/zosfiles/__tests__/__unit__/methods/upload/Upload.unit.test.ts +++ b/packages/zosfiles/__tests__/__unit__/methods/upload/Upload.unit.test.ts @@ -22,7 +22,6 @@ import { IUploadOptions } from "../../../../src/methods/upload/doc/IUploadOption import { Upload } from "../../../../src/methods/upload/Upload"; import { List } from "../../../../src/methods/list/List"; import { Utilities } from "../../../../src/methods/utilities/Utilities"; - import { ZosFilesUtils } from "../../../../src/utils/ZosFilesUtils"; import { stripNewLines } from "../../../../../../__tests__/__src__/TestUtils"; import { Create } from "../../../../src/methods/create"; @@ -379,7 +378,7 @@ describe("z/OS Files - Upload", () => { expect(error).toBeDefined(); expect(error).toBe(testError); }); - it("should return with proper response when upload buffer to a data set", async () => { + it("should return with proper response when upload buffer to a data set - buffer less than 10 chars", async () => { const buffer: Buffer = Buffer.from("testing"); const endpoint = path.posix.join(ZosFilesConstants.RESOURCE, ZosFilesConstants.RES_DS_FILES, dsName); const reqHeaders = [ZosmfHeaders.X_IBM_TEXT, ZosmfHeaders.ACCEPT_ENCODING]; @@ -392,6 +391,27 @@ describe("z/OS Files - Upload", () => { expect(error).toBeUndefined(); expect(response).toBeDefined(); + expect(response.apiResponse).toMatchObject({"from": "", "success": true, "to": dsName}); + + expect(zosmfPutFullSpy).toHaveBeenCalledTimes(1); + expect(zosmfPutFullSpy).toHaveBeenCalledWith(dummySession, {resource: endpoint, + reqHeaders, + writeData: buffer}); + }); + it("should return with proper response when upload buffer to a data set - buffer more than 10 chars", async () => { + const buffer: Buffer = Buffer.from("bufferLargerThan10Chars"); + const endpoint = path.posix.join(ZosFilesConstants.RESOURCE, ZosFilesConstants.RES_DS_FILES, dsName); + const reqHeaders = [ZosmfHeaders.X_IBM_TEXT, ZosmfHeaders.ACCEPT_ENCODING]; + + try { + response = await Upload.bufferToDataSet(dummySession, buffer, dsName); + } catch (err) { + error = err; + } + + expect(error).toBeUndefined(); + expect(response).toBeDefined(); + expect(response.apiResponse).toMatchObject({"from": "", "success": true, "to": dsName}); expect(zosmfPutFullSpy).toHaveBeenCalledTimes(1); expect(zosmfPutFullSpy).toHaveBeenCalledWith(dummySession, {resource: endpoint, @@ -748,6 +768,7 @@ describe("z/OS Files - Upload", () => { expect(error).toBeUndefined(); expect(response).toBeDefined(); + expect(response.apiResponse).toMatchObject({"from": "[Readable]", "success": true, "to": dsName}); expect(zosmfPutFullSpy).toHaveBeenCalledTimes(1); expect(zosmfPutFullSpy).toHaveBeenCalledWith(dummySession, {resource: endpoint, @@ -1661,6 +1682,9 @@ describe("z/OS Files - Upload", () => { expect(zosmfExpectSpy).toHaveBeenCalledWith(dummySession, { reqHeaders: headers, resource: endpoint, writeData: data }); }); it("should return with proper response when upload USS file in binary", async () => { + const chtagSpy = jest.spyOn(Utilities, "chtag"); + chtagSpy.mockImplementation(async (): Promise => null); + const data: Buffer = Buffer.from("testing"); const endpoint = path.posix.join(ZosFilesConstants.RESOURCE, ZosFilesConstants.RES_USS_FILES, dsName); const headers = [ZosmfHeaders.OCTET_STREAM, ZosmfHeaders.X_IBM_BINARY, ZosmfHeaders.ACCEPT_ENCODING]; @@ -1673,7 +1697,7 @@ describe("z/OS Files - Upload", () => { expect(error).toBeUndefined(); expect(USSresponse).toBeDefined(); - + expect(chtagSpy).toHaveBeenCalled(); expect(zosmfExpectSpy).toHaveBeenCalledTimes(1); expect(zosmfExpectSpy).toHaveBeenCalledWith(dummySession, { reqHeaders: headers, resource: endpoint, writeData: data }); }); @@ -1752,6 +1776,7 @@ describe("z/OS Files - Upload", () => { expect(error).toBeUndefined(); expect(USSresponse).toBeDefined(); + expect(USSresponse.apiResponse).toMatchObject({"from": "", "success": true, "to": dsName}); const normalizedData = ZosFilesUtils.normalizeNewline(data); expect(data.length).not.toBe(normalizedData.length); @@ -1827,6 +1852,7 @@ describe("z/OS Files - Upload", () => { expect(error).toBeUndefined(); expect(USSresponse).toBeDefined(); + expect(USSresponse.apiResponse).toMatchObject({"from": "[Readable]", "success": true, "to": dsName}); expect(USSresponse.success).toBeTruthy(); expect(zosmfExpectFullSpy).toHaveBeenCalledTimes(1); diff --git a/packages/zosfiles/package.json b/packages/zosfiles/package.json index 0180e6c6a7..a3d961cdec 100644 --- a/packages/zosfiles/package.json +++ b/packages/zosfiles/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-files-for-zowe-sdk", - "version": "8.8.4", + "version": "8.10.1", "description": "Zowe SDK to interact with files and data sets on z/OS", "author": "Zowe", "license": "EPL-2.0", @@ -50,10 +50,10 @@ "minimatch": "^9.0.5" }, "devDependencies": { - "@zowe/cli-test-utils": "8.8.3", - "@zowe/core-for-zowe-sdk": "8.8.3", - "@zowe/imperative": "8.8.3", - "@zowe/zos-uss-for-zowe-sdk": "8.8.3" + "@zowe/cli-test-utils": "8.10.1", + "@zowe/core-for-zowe-sdk": "8.10.1", + "@zowe/imperative": "8.10.1", + "@zowe/zos-uss-for-zowe-sdk": "8.10.1" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0", diff --git a/packages/zosfiles/src/methods/upload/Upload.ts b/packages/zosfiles/src/methods/upload/Upload.ts index 528150b961..af363ef988 100644 --- a/packages/zosfiles/src/methods/upload/Upload.ts +++ b/packages/zosfiles/src/methods/upload/Upload.ts @@ -30,7 +30,7 @@ import { Utilities, Tag } from "../utilities"; import { Readable } from "stream"; import { CLIENT_PROPERTY } from "../../doc/types/ZosmfRestClientProperties"; import { TransferMode } from "../../utils/ZosFilesAttributes"; - +import { inspect } from "util"; export class Upload { @@ -183,8 +183,18 @@ export class Upload { } const uploadRequest: IRestClientResponse = await ZosmfRestClient.putExpectFullResponse(session, requestOptions); + const maxBufferPreviewSize = 10; // By default, apiResponse is empty when uploading - const apiResponse: any = {}; + const apiResponse: any = { + success: true, + from: + fileBuffer.length > maxBufferPreviewSize + ? inspect( + fileBuffer.subarray(0, maxBufferPreviewSize) + ).slice(0, -1) + "...>" + : inspect(fileBuffer), + to: dataSetName, + }; // Return Etag in apiResponse, if requested if (options.returnEtag) { @@ -242,7 +252,11 @@ export class Upload { const uploadRequest: IRestClientResponse = await ZosmfRestClient.putExpectFullResponse(session, requestOptions); // By default, apiResponse is empty when uploading - const apiResponse: any = {}; + const apiResponse: any = { + success: true, + from: inspect(fileStream, { showHidden: false, depth: -1}), + to: dataSetName + }; // Return Etag in apiResponse, if requested if (options.returnEtag) { @@ -457,6 +471,7 @@ export class Upload { ImperativeExpect.toNotBeEqual(options.record, true, ZosFilesMessages.unsupportedDataType.message); options.binary = options.binary ? options.binary : false; ImperativeExpect.toNotBeNullOrUndefined(ussname, ZosFilesMessages.missingUSSFileName.message); + const origUssname = ussname; ussname = ZosFilesUtils.sanitizeUssPathForRestCall(ussname); const endpoint = ZosFilesConstants.RESOURCE + ZosFilesConstants.RES_USS_FILES + "/" + ussname; @@ -479,14 +494,30 @@ export class Upload { } const uploadRequest: IRestClientResponse = await ZosmfRestClient.putExpectFullResponse(session, requestOptions); + const maxBufferPreviewSize = 10; // By default, apiResponse is empty when uploading - const apiResponse: any = {}; + const apiResponse: any = { + success: true, + from: + fileBuffer.length > maxBufferPreviewSize + ? inspect( + fileBuffer.subarray(0, maxBufferPreviewSize) + ).slice(0, -1) + "...>" + : inspect(fileBuffer), + to: origUssname, + }; // Return Etag in apiResponse, if requested if (options.returnEtag) { apiResponse.etag = uploadRequest.response.headers.etag; } + if (options.encoding != null) { + await Utilities.chtag(session, origUssname, Tag.TEXT, options.encoding); + } else if (options.binary) { + await Utilities.chtag(session, origUssname, Tag.BINARY); + } + return { success: true, commandResponse: ZosFilesMessages.dataSetUploadedSuccessfully.message, @@ -534,7 +565,11 @@ export class Upload { } // By default, apiResponse is empty when uploading - const apiResponse: any = {}; + const apiResponse: any = { + success: true, + from: inspect(uploadStream, { showHidden: false, depth: -1}), + to: origUssname + }; // Return Etag in apiResponse, if requested if (options.returnEtag) { @@ -890,7 +925,6 @@ export class Upload { */ private static get log(): Logger { return Logger.getAppLogger(); - // return Logger.getConsoleLogger(); } @@ -915,7 +949,6 @@ export class Upload { const response: IUploadDir[] = []; if (Upload.hasDirs(dirPath)) { const directories = fs.readdirSync(dirPath).filter((file) => IO.isDir(path.normalize(path.join(dirPath, file)))); - // directories = directories.filter((file) => IO.isDir(path.normalize(path.join(dirPath, file)))); for (let index = 0; index < directories.length; index++) { const dirFullPath = path.normalize(path.join(dirPath, directories[index])); response.push({ diff --git a/packages/zosjobs/package.json b/packages/zosjobs/package.json index cc0421ed2a..cdd98b4ab8 100644 --- a/packages/zosjobs/package.json +++ b/packages/zosjobs/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-jobs-for-zowe-sdk", - "version": "8.8.4", + "version": "8.10.1", "description": "Zowe SDK to interact with jobs on z/OS", "author": "Zowe", "license": "EPL-2.0", @@ -46,12 +46,12 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "dependencies": { - "@zowe/zos-files-for-zowe-sdk": "8.8.4" + "@zowe/zos-files-for-zowe-sdk": "8.10.1" }, "devDependencies": { - "@zowe/cli-test-utils": "8.8.3", - "@zowe/core-for-zowe-sdk": "8.8.3", - "@zowe/imperative": "8.8.3" + "@zowe/cli-test-utils": "8.10.1", + "@zowe/core-for-zowe-sdk": "8.10.1", + "@zowe/imperative": "8.10.1" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0", diff --git a/packages/zoslogs/package.json b/packages/zoslogs/package.json index 954d6aa8cb..df452cd7ca 100644 --- a/packages/zoslogs/package.json +++ b/packages/zoslogs/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-logs-for-zowe-sdk", - "version": "8.8.3", + "version": "8.10.1", "description": "Zowe SDK to interact with the z/OS logs", "author": "Zowe", "license": "EPL-2.0", @@ -45,9 +45,9 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "devDependencies": { - "@zowe/cli-test-utils": "8.8.3", - "@zowe/core-for-zowe-sdk": "8.8.3", - "@zowe/imperative": "8.8.3" + "@zowe/cli-test-utils": "8.10.1", + "@zowe/core-for-zowe-sdk": "8.10.1", + "@zowe/imperative": "8.10.1" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0", diff --git a/packages/zosmf/package.json b/packages/zosmf/package.json index 1dee4b9536..e25ea44139 100644 --- a/packages/zosmf/package.json +++ b/packages/zosmf/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zosmf-for-zowe-sdk", - "version": "8.8.3", + "version": "8.10.1", "description": "Zowe SDK to interact with the z/OS Management Facility", "author": "Zowe", "license": "EPL-2.0", @@ -44,9 +44,9 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "devDependencies": { - "@zowe/cli-test-utils": "8.8.3", - "@zowe/core-for-zowe-sdk": "8.8.3", - "@zowe/imperative": "8.8.3" + "@zowe/cli-test-utils": "8.10.1", + "@zowe/core-for-zowe-sdk": "8.10.1", + "@zowe/imperative": "8.10.1" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0", diff --git a/packages/zostso/package.json b/packages/zostso/package.json index 92baabc181..e9bb16f105 100644 --- a/packages/zostso/package.json +++ b/packages/zostso/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-tso-for-zowe-sdk", - "version": "8.8.3", + "version": "8.10.1", "description": "Zowe SDK to interact with TSO on z/OS", "author": "Zowe", "license": "EPL-2.0", @@ -45,12 +45,12 @@ "prepack": "node ../../scripts/prepareLicenses.js" }, "dependencies": { - "@zowe/zosmf-for-zowe-sdk": "8.8.3" + "@zowe/zosmf-for-zowe-sdk": "8.10.1" }, "devDependencies": { - "@zowe/cli-test-utils": "8.8.3", - "@zowe/core-for-zowe-sdk": "8.8.3", - "@zowe/imperative": "8.8.3" + "@zowe/cli-test-utils": "8.10.1", + "@zowe/core-for-zowe-sdk": "8.10.1", + "@zowe/imperative": "8.10.1" }, "peerDependencies": { "@zowe/core-for-zowe-sdk": "^8.0.0", diff --git a/packages/zosuss/package.json b/packages/zosuss/package.json index 70da59fb65..2b5821dfe7 100644 --- a/packages/zosuss/package.json +++ b/packages/zosuss/package.json @@ -1,6 +1,6 @@ { "name": "@zowe/zos-uss-for-zowe-sdk", - "version": "8.8.3", + "version": "8.10.1", "description": "Zowe SDK to interact with USS on z/OS", "author": "Zowe", "license": "EPL-2.0", @@ -49,8 +49,8 @@ }, "devDependencies": { "@types/ssh2": "^1.11.19", - "@zowe/cli-test-utils": "8.8.3", - "@zowe/imperative": "8.8.3" + "@zowe/cli-test-utils": "8.10.1", + "@zowe/imperative": "8.10.1" }, "peerDependencies": { "@zowe/imperative": "^8.0.0" diff --git a/prototypes/Readme.md b/prototypes/Readme.md new file mode 100644 index 0000000000..763191fe3d --- /dev/null +++ b/prototypes/Readme.md @@ -0,0 +1,11 @@ +The purpose of this **prototypes** folder is to provide a home for prototype or proof-of-concept code. As an example, new, experimental code that implements a compelling feature may be good model for the future permanent implementation of that feature. + +During experiments, you might place a new class into a particular package to confirm that an idea is feasible. However, that code may not be complete enough to be built and packaged into the product. Lint rules might fail on this new code. You may not have any tests yet, so code coverage verification may fail. + +You do not want this new code to cause Zowe build pipelines to fail, but you do not want to lose the useful work that has been completed. Placing your source file(s) under the **prototypes** directory is a way to keep your valuable experiment and not break the Zowe build process. + +Place your source file into a path under the **prototypes** directory that mirrors the real directory path. + +**Tip:** Create a hard link from the file in the real file path to an identical file path under the **prototypes** directory. That way imports, compiles, and debugging will work in the real directory during experiments, while all changes are automatically recorded in the **prototypes** path. + +**Note:** Remember to delete a new file or revert an existing file in the real directory path. The remaining hard-linked file(s) in the **prototypes** directory path will still have all of the latest changes. diff --git a/prototypes/packages/.gitkeep b/prototypes/packages/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/prototypes/packages/cli/src/zosfiles/list/ds/DataSet.handler.ts b/prototypes/packages/cli/src/zosfiles/list/ds/DataSet.handler.ts new file mode 100644 index 0000000000..e918eb5e8a --- /dev/null +++ b/prototypes/packages/cli/src/zosfiles/list/ds/DataSet.handler.ts @@ -0,0 +1,47 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +import { AbstractSession, IHandlerParameters, TextUtils } from "@zowe/imperative"; +import { IZosFilesResponse, List } from "@zowe/zos-files-for-zowe-sdk"; +import { ZosFilesBaseHandler } from "../../ZosFilesBase.handler"; + +/** + * Handler to list a data sets + * @export + */ +export default class DataSetHandler extends ZosFilesBaseHandler { + public async processWithSession(commandParameters: IHandlerParameters, session: AbstractSession): Promise { + /* todo: Remove diagnostic print statements + */ + return { + commandResponse: "____ DataSetHandler: Pretend that we ran the list command.", + success: true + }; + // end Remove todo: */ + + const response = await List.dataSet(session, commandParameters.arguments.dataSetName, { + volume: commandParameters.arguments.volumeSerial, + attributes: commandParameters.arguments.attributes, + maxLength: commandParameters.arguments.maxLength, + responseTimeout: commandParameters.arguments.responseTimeout, + start: commandParameters.arguments.start + }); + + if (commandParameters.arguments.attributes && response.apiResponse.items.length > 0) { + commandParameters.response.console.log(TextUtils.prettyJson(response.apiResponse.items)); + } else { + const dsnameList = response.apiResponse.items.map((mem: any) => mem.dsname); + commandParameters.response.console.log(dsnameList.join("\n")); + } + + return response; + } +} diff --git a/prototypes/packages/cli/src/zosfiles/list/ds/Readme.md b/prototypes/packages/cli/src/zosfiles/list/ds/Readme.md new file mode 100644 index 0000000000..5a1554004d --- /dev/null +++ b/prototypes/packages/cli/src/zosfiles/list/ds/Readme.md @@ -0,0 +1,7 @@ +Files used to create a prototype of AuthOrder are: + +prototypes\packages\imperative\src\rest\src\session\AuthOrder.ts + +prototypes\packages\imperative\src\rest\src\session\ConnectionPropsForSessCfg.ts + +prototypes\packages\cli\src\zosfiles\list\ds\DataSet.handler.ts diff --git a/prototypes/packages/imperative/src/rest/src/session/AuthOrder.ts b/prototypes/packages/imperative/src/rest/src/session/AuthOrder.ts new file mode 100644 index 0000000000..dade7829d5 --- /dev/null +++ b/prototypes/packages/imperative/src/rest/src/session/AuthOrder.ts @@ -0,0 +1,316 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +import { ICommandArguments } from "../../../cmd"; +import { ImperativeError } from "../../../error"; +import { ISession } from "./doc/ISession"; +import { Logger } from "../../../logger"; +import * as SessConstants from "./SessConstants"; + +/** + * @internal - Cannot be used outside of the imperative package + * + * The purpose of this class is to detect an authentication order property + * supplied by a user in a profile, command line, or environment variable. + * That authOrder is then used to place the correct set of credentials into + * a session for authentication. + */ +export class AuthOrder { + + /** + * When a user does not supply an auth order, Zowe clients will use a + * hard-coded default order. This property records whether AUTH_TYPE_BASIC + * or AUTH_TYPE_TOKEN is the top auth choice in the order. + */ + private static m_topDefaultAuth: string = SessConstants.AUTH_TYPE_BASIC; + + /** + * This array of authentication types specifies the order of preferred + * authentication. It contains the user-specified order, or a default order + * if the user does not specify an order. m_authOrder[0] is the highest + * preferred authentication. + */ + private static m_authOrder: SessConstants.AUTH_TYPE_CHOICES[] = null; + + // *********************************************************************** + /** + * Set the top auth type when a default authOrder is used. Previously, + * two different hard-coded orders were present in the Zowe clients. + * Both hard-coded orders are now provided by this class. Zowe code, + * that still sets a specific order for backward compatibility, now + * calls this function to ensure that that the original behavior remains + * the same and avoids a breaking change when a user has not specified + * an authOrder property. + * + * @param topDefaultAuth - Input. + * The top authentication type that will be used when forming a + * default authOrder. + */ + public static setTopDefaultAuth( + topDefaultAuth: typeof SessConstants.AUTH_TYPE_BASIC | typeof SessConstants.AUTH_TYPE_TOKEN + ): void { + AuthOrder.m_topDefaultAuth = topDefaultAuth; + } + + // *********************************************************************** + /** + * Cache the authOrder property from the supplied cmdArgs. If no authOrder exists + * in cmdArgs, a default authOrder is created and cached. + * + * @param cmdArgs - Input. + * The set of arguments that the calling function is using. + */ + private static cacheAuthOrder(cmdArgs: ICommandArguments): void { + // have we already cached the authOrder? + if (AuthOrder.m_authOrder !== null) { + // start over with an empty order. + AuthOrder.m_authOrder = null; + } + + if (cmdArgs.authOrder) { + // validate each user-supplied type of authentication + for (const nextUserAuth of cmdArgs.authOrder) { + switch (nextUserAuth) { + case SessConstants.AUTH_TYPE_BASIC: + case SessConstants.AUTH_TYPE_TOKEN: + case SessConstants.AUTH_TYPE_BEARER: + case SessConstants.AUTH_TYPE_CERT_PEM: + case SessConstants.AUTH_TYPE_NONE: + if (AuthOrder.m_authOrder === null) { + AuthOrder.m_authOrder = []; + } + AuthOrder.m_authOrder.push(nextUserAuth); + break; + default: + Logger.getImperativeLogger().error( + `The authentication = '${nextUserAuth}' is not valid and will be ignored.` + ); + // todo: Remove diagnostic print statements + console.log("____ cacheAuthOrder: nextUserAuth = '" + nextUserAuth + "' is not valid and will be ignored."); + // todo: end Remove + } + } + } + + // the user supplied an authOrder + if (AuthOrder.m_authOrder !== null) { + return; + } + + // No authOrder was supplied by the user. Create a default order. + AuthOrder.m_authOrder = []; + if (AuthOrder.m_topDefaultAuth === SessConstants.AUTH_TYPE_BASIC) { + // we want user & password auth as the top choice + AuthOrder.m_authOrder.push(SessConstants.AUTH_TYPE_BASIC); + AuthOrder.m_authOrder.push(SessConstants.AUTH_TYPE_TOKEN); + } else { + // we want token auth as the top choice + AuthOrder.m_authOrder.push(SessConstants.AUTH_TYPE_TOKEN); + AuthOrder.m_authOrder.push(SessConstants.AUTH_TYPE_BASIC); + } + // add remaining auth types. We do not include 'none' in our defaults. + AuthOrder.m_authOrder.push(SessConstants.AUTH_TYPE_BEARER); + AuthOrder.m_authOrder.push(SessConstants.AUTH_TYPE_CERT_PEM); + } + + // *********************************************************************** + /** + * Find the highest auth type (according to the authOrder) which exists + * in either the supplied session config or command line arguments. + * Then place the credentials associated with that auth type into the + * supplied session config. Credentials for all other auth types are + * removed from the session config. + * + * @param sessCfg - Modified. + * Authentication properties are added to and removed from this + * session configuration, which can already have properties in + * this object when passed to this function. + * + * @param cmdArgs - Input. + * The set of arguments with which the calling function is operating. + * For CLI, the cmdArgs come from the command line, profile, or + * environment. Other apps can place relevant arguments into this + * object to be processed by this function. + */ + public static putTopAuthInSession( + sessCfg: SessCfgType, + cmdArgs: ICommandArguments + ): void { + let sessTypeToUse: SessConstants.AUTH_TYPE_CHOICES = null; + + // todo: Remove diagnostic print statements + console.log("____ putTopAuthInSession: cmdArgs = " + JSON.stringify(cmdArgs, null, 2)); + sessCfg.tokenType = "apimlAuthenticationToken"; + sessCfg.tokenValue = "SomeTokenValue"; + sessCfg.cert = "./certFile.txt"; + sessCfg.certKey = "./certKeyFile.txt"; + sessCfg.authTypeOrder = []; + sessCfg.authTypeOrder.push("bogusAuthType1"); + sessCfg.authTypeOrder.push("bogusAuthType2"); + sessCfg.authTypeOrder.push("bogusAuthType3"); + sessCfg.authTypeOrder.push("bogusAuthType4"); + console.log("____ putTopAuthInSession: sessCfg before processing = " + JSON.stringify(sessCfg, null, 2)); + // todo: end Remove + + // cache the correct authOrder to use + AuthOrder.cacheAuthOrder(cmdArgs); + + // Detect the first auth type (from our auth order) provided in the session config or in command args. + // Ensure that the auth properties are placed in the session config. + // Record the detected auth type for use as the session type. + let errMsg: string; + for (const nextAuth of AuthOrder.m_authOrder) { + switch (nextAuth) { + case SessConstants.AUTH_TYPE_BASIC: + // todo: do we have to check for sessCfg.base64EncodedAuth ? + if (cmdArgs.user?.length > 0) { + sessCfg.user = cmdArgs.user; + } + if (cmdArgs.password?.length > 0) { + sessCfg.password = cmdArgs.password; + } + if (sessCfg.user?.length > 0 && sessCfg.password?.length > 0) { + // TODO: Confirm in ConnectionPropsForSessCfg that requestToken will work ok with sessTypeToUse + sessTypeToUse = SessConstants.AUTH_TYPE_BASIC; + } + break; + case SessConstants.AUTH_TYPE_TOKEN: + if (cmdArgs.tokenType?.length > 0) { + sessCfg.tokenType = cmdArgs.tokenType; + } + if (cmdArgs.tokenValue?.length > 0) { + sessCfg.tokenValue = cmdArgs.tokenValue; + } + if (sessCfg.tokenType?.length > 0 && sessCfg.tokenValue?.length > 0) { + sessTypeToUse = SessConstants.AUTH_TYPE_TOKEN; + } + break; + case SessConstants.AUTH_TYPE_BEARER: + if (cmdArgs.tokenType?.length > 0) { + sessCfg.tokenType = cmdArgs.tokenType; + } + if (cmdArgs.tokenValue?.length > 0) { + sessCfg.tokenValue = cmdArgs.tokenValue; + } + // a tokenValue with no tokenType implies a bearer token + if (!(sessCfg.tokenType?.length > 0) && sessCfg.tokenValue?.length > 0) { + sessTypeToUse = SessConstants.AUTH_TYPE_BEARER; + } + break; + case SessConstants.AUTH_TYPE_CERT_PEM: + if (cmdArgs.certFile?.length > 0) { + sessCfg.cert = cmdArgs.certFile; + } + if (cmdArgs.certKeyFile?.length > 0) { + sessCfg.certKey = cmdArgs.certKeyFile; + } + if (sessCfg.cert?.length > 0 && sessCfg.certKey?.length > 0) { + sessTypeToUse = SessConstants.AUTH_TYPE_CERT_PEM; + } + break; + case SessConstants.AUTH_TYPE_NONE: + sessTypeToUse = SessConstants.AUTH_TYPE_NONE; + break; + default: + // authOrder was validated. A wrong value now is our programming error. + errMsg = `authOrder contains an invalid authentication = ${nextAuth}.`; + Logger.getImperativeLogger().error(errMsg); + throw new ImperativeError({ msg: errMsg }); + } + if (sessTypeToUse !== null) { + // stop looking for auth types after we find the first one + break; + } + } + + // When no creds are in the session and AUTH_TYPE_NONE is not in the user's authOrder, + // remove the session type from the session. Otherwise set the type that we found. + if (sessTypeToUse === null) { + delete sessCfg.type; + } else { + sessCfg.type = sessTypeToUse; + } + + // remove all extra auth creds from the session + AuthOrder.removeExtraCredsFromSess(sessCfg); + + // copy our authOrder into the session object + sessCfg.authTypeOrder = [...AuthOrder.m_authOrder]; + + // todo: Should we throw error if no creds are in the session, or let other functions throw the error? + + // todo: Remove diagnostic print statements + console.log("____ putTopAuthInSession:\nsessCfg after processing = " + JSON.stringify(sessCfg, null, 2)); + // todo: end Remove + } + + // *********************************************************************** + /** + * Remove all credential properties from the supplied session except for the + * creds related to the session type specified within the sessCfg argument. + * + * @param sessCfg - Modified. + * Authentication credentials are removed from this session configuration. + */ + private static removeExtraCredsFromSess( + sessCfg: SessCfgType + ): void { + if (AuthOrder.cacheAuthOrder == null) { + const errMsg = "AuthOrder.cacheAuthOrder must be called before AuthOrder.removeExtraCredsFromSess"; + Logger.getImperativeLogger().error(errMsg); + throw new ImperativeError({ msg: errMsg }); + } + if (!sessCfg?.type) { + const errMsg = "Session type must exist in the supplied session."; + Logger.getImperativeLogger().error(errMsg); + throw new ImperativeError({ msg: errMsg }); + } + + // initially set all creds to be removed from the session. Later we delete the desired creds from this set + const credsToRemove = new Set(["user", "password", "base64EncodedAuth", "tokenType", "tokenValue", "cert", "certKey"]); + + // Delete the selected creds from the set of creds that will be removed from our session config. + let errMsg: string; + switch (sessCfg.type) { + case SessConstants.AUTH_TYPE_BASIC: + credsToRemove.delete("user"); + credsToRemove.delete("password"); + credsToRemove.delete("base64EncodedAuth"); + break; + case SessConstants.AUTH_TYPE_TOKEN: + credsToRemove.delete("tokenType"); + credsToRemove.delete("tokenValue"); + break; + case SessConstants.AUTH_TYPE_BEARER: + credsToRemove.delete("tokenValue"); + break; + case SessConstants.AUTH_TYPE_CERT_PEM: + credsToRemove.delete("cert"); + credsToRemove.delete("certKey"); + break; + case SessConstants.AUTH_TYPE_NONE: + break; + default: + // authOrder was validated. A wrong value now is our programming error. + errMsg = `Session an invalid type = ${sessCfg.type}.`; + Logger.getImperativeLogger().error(errMsg); + throw new ImperativeError({ msg: errMsg }); + } + + // remove all auth creds from the session, except the creds for the auth type that we chose to keep + const credIter = credsToRemove.values(); + let nextCredToRemove = credIter.next(); + while (!nextCredToRemove.done) { + delete (sessCfg as any)[nextCredToRemove.value]; + nextCredToRemove = credIter.next(); + } + } +} \ No newline at end of file diff --git a/prototypes/packages/imperative/src/rest/src/session/ConnectionPropsForSessCfg.ts b/prototypes/packages/imperative/src/rest/src/session/ConnectionPropsForSessCfg.ts new file mode 100644 index 0000000000..76ea99871b --- /dev/null +++ b/prototypes/packages/imperative/src/rest/src/session/ConnectionPropsForSessCfg.ts @@ -0,0 +1,569 @@ +/* +* This program and the accompanying materials are made available under the terms of the +* Eclipse Public License v2.0 which accompanies this distribution, and is available at +* https://www.eclipse.org/legal/epl-v20.html +* +* SPDX-License-Identifier: EPL-2.0 +* +* Copyright Contributors to the Zowe Project. +* +*/ + +import { CliUtils, ImperativeConfig, TextUtils } from "../../../utilities"; +import { ICommandArguments, IHandlerParameters } from "../../../cmd"; +import { ImperativeError } from "../../../error"; +import { IOptionsForAddConnProps } from "./doc/IOptionsForAddConnProps"; +import { Logger } from "../../../logger"; +import * as SessConstants from "./SessConstants"; +import { IPromptOptions } from "../../../cmd/src/doc/response/api/handler/IPromptOptions"; +import { ISession } from "./doc/ISession"; +import { IProfileProperty } from "../../../profiles"; +import { ConfigAutoStore } from "../../../config/src/ConfigAutoStore"; +import { ConfigUtils } from "../../../config/src/ConfigUtils"; +import { AuthOrder } from "./AuthOrder"; + +/** + * Extend options for IPromptOptions for internal wrapper method + * @interface IHandlePromptOptions + * @extends {IPromptOptions} + */ +interface IHandlePromptOptions extends IPromptOptions { + + /** + * Adds IHandlerParameters to IPromptOptions + * @type {IHandlerParameters} + * @memberof IHandlePromptOptions + */ + parms?: IHandlerParameters; +} + +/** + * This class adds connection information to an existing session configuration + * object for making REST API calls with the Imperative RestClient. + */ +export class ConnectionPropsForSessCfg { + + // *********************************************************************** + /** + * Create a REST session configuration object starting with the supplied + * initialSessCfg and retrieving connection properties from the command + * line arguments (or environment, or profile). If required connection + * properties are missing we interactively prompt the user for the values. + * for any of the following properties: + * host + * port + * user name + * password + * + * Any prompt will timeout after 30 seconds so that this function can + * be run from an automated script, and will not indefinitely hang that + * script. + * + * In addition to properties that we prompt for, we will also add the following + * properties to the session configuration if they are available. + * type + * tokenType + * tokenValue + * + * @param initialSessCfg + * An initial session configuration (like ISession, or other + * specially defined configuration) that contains your desired + * session configuration properties. + * + * @param cmdArgs + * The arguments specified by the user on the command line + * (or in environment, or in profile). The contents of the + * supplied cmdArgs will be modified. + * + * @param connOpts + * Options that alter our connection actions. See IOptionsForAddConnProps. + * The connOpts parameter need not be supplied. + * + * @example + * // Within the process() function of a command handler, + * // do steps similar to the following: + * const sessCfg: ISession = { + * rejectUnauthorized: commandParameters.arguments.rejectUnauthorized, + * basePath: commandParameters.arguments.basePath + * }; + * const connectableSessCfg = await ConnectionPropsForSessCfg.addPropsOrPrompt( + * sessCfg, commandParameters.arguments + * ); + * mySession = new Session(connectableSessCfg); + * + * @returns A session configuration object with connection information + * added to the initialSessCfg. Its intended use is for our + * caller to create a session for a REST Client. + */ + public static async addPropsOrPrompt( + initialSessCfg: SessCfgType, + cmdArgs: ICommandArguments, + connOpts: IOptionsForAddConnProps = {} + ): Promise { + const impLogger = Logger.getImperativeLogger(); + + /* Create copies of our initialSessCfg and connOpts so that + * we can modify them without changing the caller's copy. + */ + const sessCfgToUse = { ...initialSessCfg }; + const connOptsToUse = { ...connOpts }; + + // resolve all values between sessCfg and cmdArgs using option choices + ConnectionPropsForSessCfg.resolveSessCfgProps( + sessCfgToUse, cmdArgs, connOptsToUse + ); + + // This function will provide all the needed properties in one array + let promptForValues: (keyof SessCfgType & string)[] = []; + const doNotPromptForValues: (keyof SessCfgType & string)[] = []; + + /* Add the override properties to the session object. + */ + if (connOpts.propertyOverrides?.length > 0) { + for (const override of connOpts.propertyOverrides) { + const argName = override.argumentName ?? override.propertyName; + // If the override is found on the session or command arguments, start setting things and do not prompt for overridden properties + if ((sessCfgToUse as any)[override.propertyName] != null || cmdArgs[argName] != null) { + // Set the session config to use the command line argument if it exists. + if (cmdArgs[argName] != null) { (sessCfgToUse as any)[override.propertyName] = cmdArgs[argName]; } + for (const prop of override.propertiesOverridden) { + // Make sure we do not prompt for the overridden property. + if (!doNotPromptForValues.includes(prop)) { doNotPromptForValues.push(prop); } + if (prop in sessCfgToUse) { (sessCfgToUse as any)[prop] = undefined; } + } + } + } + } + + // Set default values on propsToPromptFor + if(connOpts.propsToPromptFor?.length > 0) { + connOpts.propsToPromptFor.forEach(obj => { + if(obj.secure == null) obj.secure = true; + if(obj.secure) this.secureSessCfgProps.add(obj.name.toString()); + promptForValues.push(obj.name as keyof ISession); + this.promptTextForValues[obj.name.toString()] = obj.description; + }); + } + // check what properties are needed to be prompted + if (ConnectionPropsForSessCfg.propHasValue(sessCfgToUse.hostname) === false && !doNotPromptForValues.includes("hostname")) { + promptForValues.push("hostname"); + } + + if ((ConnectionPropsForSessCfg.propHasValue(sessCfgToUse.port) === false || sessCfgToUse.port === 0) && + !doNotPromptForValues.includes("port")) { + promptForValues.push("port"); + } + + const isTokenIrrelevant = ConnectionPropsForSessCfg.propHasValue(sessCfgToUse.tokenValue) === false || + connOpts.supportedAuthTypes && !connOpts.supportedAuthTypes.includes(SessConstants.AUTH_TYPE_TOKEN); + const isCertIrrelevant = ConnectionPropsForSessCfg.propHasValue(sessCfgToUse.cert) === false || + connOpts.supportedAuthTypes && !connOpts.supportedAuthTypes.includes(SessConstants.AUTH_TYPE_CERT_PEM); + if (isTokenIrrelevant && isCertIrrelevant) { + if (ConnectionPropsForSessCfg.propHasValue(sessCfgToUse.user) === false && !doNotPromptForValues.includes("user")) { + promptForValues.push("user"); + } + + if (ConnectionPropsForSessCfg.propHasValue(sessCfgToUse.password) === false && !doNotPromptForValues.includes("password")) { + promptForValues.push("password"); + } + } + + this.loadSecureSessCfgProps(connOptsToUse.parms, promptForValues); + + if (connOptsToUse.getValuesBack == null && connOptsToUse.doPrompting) { + connOptsToUse.getValuesBack = this.getValuesBack(connOptsToUse); + } + + if (connOptsToUse.getValuesBack != null) { + // put all the needed properties in an array and call the external function + const answers = await connOptsToUse.getValuesBack(promptForValues); + + if(connOpts.propsToPromptFor?.length > 0) + { + connOpts.propsToPromptFor.forEach(obj => { + if(obj.isGivenValueValid != null) + { + if(!obj.isGivenValueValid(answers[obj.name])) promptForValues = promptForValues.filter(item => obj.name !== item); + } + }); + } + // validate what values are given back and move it to sessCfgToUse + for (const value of promptForValues) { + if (ConnectionPropsForSessCfg.propHasValue(answers[value])) { + (sessCfgToUse as any)[value] = answers[value]; + } + } + + // + if (connOptsToUse.autoStore !== false && connOptsToUse.parms != null) { + await ConfigAutoStore.storeSessCfgProps(connOptsToUse.parms, sessCfgToUse, promptForValues); + } + } + + AuthOrder.putTopAuthInSession(sessCfgToUse, cmdArgs); + + impLogger.debug("Session config after any prompting for missing values:"); + ConnectionPropsForSessCfg.logSessCfg(sessCfgToUse); + return sessCfgToUse; + } + + // *********************************************************************** + /** + * Resolve the overlapping or mutually exclusive properties that can + * occur. Ensure that the resulting session configuration object contains + * only the applicable properties. The contents of the supplied sessCfg, + * cmdArgs, and connOpts will be modified. + * + * @param sessCfg + * An initial session configuration that contains your desired + * session configuration properties. + * + * @param cmdArgs + * The arguments specified by the user on the command line + * (or in environment, or in profile) + * + * @param connOpts + * Options that alter our actions. See IOptionsForAddConnProps. + * The connOpts parameter need not be supplied. + * The only option values used by this function are: + * connOpts.requestToken + * connOpts.defaultTokenType + * + * @example + * let sessCfg = YouCollectAllProfilePropertiesRelatedToSession(); + * let cmdArgs = YouSetPropertiesRequiredInCmdArgs(); + * ConnectionPropsForSessCfg.resolveSessCfgProps(sessCfg, cmdArgs); + * sessionToUse = new Session(sessCfg); + */ + public static resolveSessCfgProps( + sessCfg: SessCfgType, + cmdArgs: ICommandArguments = { $0: "", _: [] }, + connOpts: IOptionsForAddConnProps = {} + ) { + const impLogger = Logger.getImperativeLogger(); + + // use defaults if caller has not specified these properties. + if (!Object.prototype.hasOwnProperty.call(connOpts, "requestToken")) { + connOpts.requestToken = false; + } + if (!Object.prototype.hasOwnProperty.call(connOpts, "doPrompting")) { + connOpts.doPrompting = true; + } + if (!Object.prototype.hasOwnProperty.call(connOpts, "defaultTokenType")) { + connOpts.defaultTokenType = SessConstants.TOKEN_TYPE_JWT; + } + + /* Override properties from our caller's sessCfg + * with any values from the command line. + */ + if (ConnectionPropsForSessCfg.propHasValue(cmdArgs.host)) { + sessCfg.hostname = cmdArgs.host; + } + if (ConnectionPropsForSessCfg.propHasValue(cmdArgs.port)) { + sessCfg.port = cmdArgs.port; + } + if (ConnectionPropsForSessCfg.propHasValue(cmdArgs.user)) { + sessCfg.user = cmdArgs.user; + } + if (ConnectionPropsForSessCfg.propHasValue(cmdArgs.password)) { + sessCfg.password = cmdArgs.password; + } + + if (connOpts.requestToken) { + // deleting any tokenValue, ensures that basic creds are used to authenticate and get token + delete sessCfg.tokenValue; + } else if (ConnectionPropsForSessCfg.propHasValue(sessCfg.user) === false && + ConnectionPropsForSessCfg.propHasValue(sessCfg.password) === false && + ConnectionPropsForSessCfg.propHasValue(cmdArgs.tokenValue)) { + // set tokenValue if token is in args, and user and password are NOT supplied. + sessCfg.tokenValue = cmdArgs.tokenValue; + } + + // we use a cert when none of user, password, or token are supplied + if (ConnectionPropsForSessCfg.propHasValue(sessCfg.user) === false && + ConnectionPropsForSessCfg.propHasValue(sessCfg.password) === false && + ConnectionPropsForSessCfg.propHasValue(sessCfg.tokenValue) === false && + ConnectionPropsForSessCfg.propHasValue(cmdArgs.certFile)) { + if (ConnectionPropsForSessCfg.propHasValue(cmdArgs.certKeyFile)) { + sessCfg.cert = cmdArgs.certFile; + sessCfg.certKey = cmdArgs.certKeyFile; + } + // else if (ConnectionPropsForSessCfg.propHasValue(cmdArgs.certFilePassphrase)) { + // sessCfg.cert = cmdArgs.certFile; + // sessCfg.passphrase = cmdArgs.certFilePassphrase; + // } + } + + const isTokenUsed = ConnectionPropsForSessCfg.propHasValue(sessCfg.tokenValue) && + (connOpts.supportedAuthTypes == null || connOpts.supportedAuthTypes.includes(SessConstants.AUTH_TYPE_TOKEN)); + const isCertUsed = ConnectionPropsForSessCfg.propHasValue(sessCfg.cert) && + (connOpts.supportedAuthTypes == null || connOpts.supportedAuthTypes.includes(SessConstants.AUTH_TYPE_CERT_PEM)); + if (isTokenUsed) { + // when tokenValue is set at this point, we are definitely using the token. + impLogger.debug("Using token authentication"); + + // override any token type in sessCfg with cmdArgs value + if (ConnectionPropsForSessCfg.propHasValue(cmdArgs.tokenType)) { + sessCfg.tokenType = cmdArgs.tokenType; + } + + // set the auth type based on token type + if (ConnectionPropsForSessCfg.propHasValue(sessCfg.tokenType)) { + sessCfg.type = SessConstants.AUTH_TYPE_TOKEN; + } else { + // When no tokenType supplied, user wants bearer + sessCfg.type = SessConstants.AUTH_TYPE_BEARER; + } + } else if (isCertUsed) { + // when cert property is set at this point, we will use the certificate + if (ConnectionPropsForSessCfg.propHasValue(sessCfg.certKey)) { + impLogger.debug("Using PEM Certificate authentication"); + sessCfg.type = SessConstants.AUTH_TYPE_CERT_PEM; + } + // else if (ConnectionPropsForSessCfg.propHasValue(sessCfg.passphrase)) { + // impLogger.debug("Using PFX Certificate authentication"); + // sessCfg.type = SessConstants.AUTH_TYPE_CERT_PFX; + // } + } else { + // we are using basic auth + impLogger.debug("Using basic authentication"); + sessCfg.type = SessConstants.AUTH_TYPE_BASIC; + } + ConnectionPropsForSessCfg.setTypeForTokenRequest(sessCfg, connOpts, cmdArgs.tokenType); + ConnectionPropsForSessCfg.logSessCfg(sessCfg); + } + + // *********************************************************************** + /** + * Confirm whether the given session has a credentials. + * + * @param sessToTest + * the session to be confirmed. + * + * @returns true is the session has credentials. false otherwise. + */ + public static sessHasCreds(sessToTest: ISession) { + if (sessToTest == null) { + return false; + } + const hasToken = sessToTest.tokenType != null && sessToTest.tokenValue != null; + const hasCert = sessToTest.certKey != null && sessToTest.cert; + const hasBasicAuth = sessToTest.base64EncodedAuth != null; + const hasCreds = sessToTest.user != null && sessToTest.password; + return hasToken || hasCert || hasBasicAuth || hasCreds; + } + + /** + * List of properties on `sessCfg` object that should be kept secret and + * may not appear in Imperative log files. + * + * NOTE(Kelosky): redundant from LoggerUtils.SECURE_PROMPT_OPTIONS - leaving + * for future date to consolidate + */ + private static secureSessCfgProps: Set = new Set(["user", "password", "tokenValue", "passphrase"]); + + /** + * List of prompt messages that is used when the CLI prompts for session + * config values. + */ + private static readonly promptTextForValues: { [key: string]: string } = { + hostname: "Enter the host name of", + port: "Enter the port number of", + user: "Enter the user name for", + password: "Enter the password for" + }; + + /** + * Prompts the user to input session config values in a CLI environment. + * This is the default implementation of the `getValuesBack` callback when + * `connOpts.doPrompting` is true. + * @param connOpts Options for adding connection properties + * @returns Name-value pairs of connection properties + */ + private static getValuesBack(connOpts: IOptionsForAddConnProps): + (properties: string[]) => Promise<{ [key: string]: any }> { + return async (promptForValues: string[]) => { + /* The check for console.log in the following 'if' statement is only needed for tests + * which do not create a mock for the connOpts.parms.response.console.log property. + * In the real world, that property always exists for this CLI-only path of logic. + */ + if (promptForValues.length > 0 && connOpts.parms?.response.console.log) { + // We need to prompt for some values. Determine why we need to prompt. + let reasonForPrompts: string = ""; + if (ImperativeConfig.instance.config?.exists) { + reasonForPrompts += "Some required connection properties have not been specified " + + "in your Zowe client configuration. "; + } else if (ConfigUtils.onlyV1ProfilesExist) { + reasonForPrompts += "Only V1 profiles exist. V1 profiles are no longer supported. " + + "You should convert your V1 profiles to a newer Zowe client configuration. "; + } else { + reasonForPrompts += "No Zowe client configuration exists. "; + } + + reasonForPrompts += "Therefore, you will be asked for the connection properties " + + "that are required to complete your command.\n"; + connOpts.parms.response.console.log(TextUtils.wordWrap( + TextUtils.chalk.yellowBright(reasonForPrompts)) + ); + } + + const answers: { [key: string]: any } = {}; + const profileSchema = this.loadSchemaForSessCfgProps(connOpts.parms, promptForValues); + const serviceDescription = connOpts.serviceDescription || "your service"; + + for (const value of promptForValues) { + let answer; + while (answer === undefined) { + const hideText = profileSchema[value]?.secure || this.secureSessCfgProps.has(value); + const valuePrompt = this.promptTextForValues[value] ?? `Enter your ${value} for`; + let promptText = `${valuePrompt} ${serviceDescription}`; + if (hideText) { + promptText += " (will be hidden)"; + } + answer = await this.clientPrompt(`${promptText}: `, { hideText, parms: connOpts.parms }); + if (answer === null) { + throw new ImperativeError({ msg: `Timed out waiting for ${value}.` }); + } + } + if (profileSchema[value]?.type === "number") { + answer = Number(answer); + if (isNaN(answer)) { + throw new ImperativeError({ msg: `Specified ${value} was not a number.` }); + } + } + answers[value] = answer; + } + + return answers; + }; + } + + /** + * Handle prompting for clients. If in a CLI environment, use the IHandlerParameters.response + * object prompt method. + * @private + * @static + * @param {string} promptText + * @param {IHandlePromptOptions} opts + * @returns {Promise} + * @memberof ConnectionPropsForSessCfg + */ + private static async clientPrompt(promptText: string, opts: IHandlePromptOptions): Promise { + if (opts.parms) { + return opts.parms.response.console.prompt(promptText, opts); + } else { + return CliUtils.readPrompt(promptText, opts); + } + } + + // *********************************************************************** + /** + * Determine if we want to request a token. + * Set the session's type and tokenType accordingly. + * + * @param sessCfg + * The session configuration to be updated. + * + * @param options + * Options that alter our actions. See IOptionsForAddConnProps. + * + * @param tokenType + * The type of token that we expect to receive. + */ + private static setTypeForTokenRequest( + sessCfg: SessCfgType, + options: IOptionsForAddConnProps, + tokenType: SessConstants.TOKEN_TYPE_CHOICES + ) { + const impLogger = Logger.getImperativeLogger(); + if (options.requestToken) { + impLogger.debug("Requesting a token"); + if (sessCfg.type === SessConstants.AUTH_TYPE_BASIC) { + // Set our type to token to get a token from user and pass + sessCfg.type = SessConstants.AUTH_TYPE_TOKEN; + } + sessCfg.tokenType = tokenType || sessCfg.tokenType || options.defaultTokenType; + } + } + + // *********************************************************************** + /** + * Log the session configuration that resulted from the addition of + * credentials. Hide the password. + * + * @param sessCfg + * The session configuration to be logged. + */ + private static logSessCfg(sessCfg: any) { + const impLogger = Logger.getImperativeLogger(); + + // create copy of sessCfg and obscure secure fields for displaying in the log + const sanitizedSessCfg = JSON.parse(JSON.stringify(sessCfg)); + for (const secureProp of ConnectionPropsForSessCfg.secureSessCfgProps) { + if (sanitizedSessCfg[secureProp] != null) { + sanitizedSessCfg[secureProp] = `${secureProp}_is_hidden`; + } + } + impLogger.debug("Creating a session config with these properties:\n" + + JSON.stringify(sanitizedSessCfg, null, 2) + ); + } + + // *********************************************************************** + /** + * Confirm whether the specified property has a value. + * + * @param propToTest + * the property key to be confirmed. + * + * @returns true is the property exists and has a value. false otherwise. + */ + private static propHasValue(propToTest: any) { + return propToTest != null && propToTest !== ""; + } + + /** + * Load base profile property schema for connection properties. + * @param params CLI handler parameters object + * @param promptForValues List of ISessCfg properties to prompt for + * @returns Key-value pairs of ISessCfg property name and profile property schema + */ + private static loadSchemaForSessCfgProps(params: IHandlerParameters | undefined, promptForValues: string[]): { [key: string]: IProfileProperty } { + if (params == null || ImperativeConfig.instance.loadedConfig?.baseProfile == null) { + return {}; + } + + const schemas: { [key: string]: IProfileProperty } = {}; + for (const propName of promptForValues) { + const profilePropName = propName === "hostname" ? "host" : propName; + schemas[propName] = ImperativeConfig.instance.loadedConfig.baseProfile.schema.properties[profilePropName]; + } + return schemas; + } + + /** + * Load list of secure property names defined in team config. + * @param params CLI handler parameters object + * @param promptForValues List of ISessCfg properties to prompt for + */ + private static loadSecureSessCfgProps(params: IHandlerParameters | undefined, promptForValues: string[]): void { + if (params == null || !ImperativeConfig.instance.config?.exists) { + return; + } + + // Find profile that includes all the properties being prompted for + const profileProps = promptForValues.map(propName => propName === "hostname" ? "host" : propName); + const profileData = ConfigAutoStore.findActiveProfile(params, profileProps); + if (profileData == null) { + return; + } + + // Load secure property names that are defined for active profiles + const config = ImperativeConfig.instance.config; + const baseProfileName = ConfigUtils.getActiveProfileName("base", params.arguments); + for (const secureProp of [...config.api.secure.securePropsForProfile(profileData[1]), + ...config.api.secure.securePropsForProfile(baseProfileName)]) { + this.secureSessCfgProps.add(secureProp === "host" ? "hostname" : secureProp); + } + } +} \ No newline at end of file diff --git a/prototypes/packages/imperative/src/rest/src/session/Readme.md b/prototypes/packages/imperative/src/rest/src/session/Readme.md new file mode 100644 index 0000000000..5a1554004d --- /dev/null +++ b/prototypes/packages/imperative/src/rest/src/session/Readme.md @@ -0,0 +1,7 @@ +Files used to create a prototype of AuthOrder are: + +prototypes\packages\imperative\src\rest\src\session\AuthOrder.ts + +prototypes\packages\imperative\src\rest\src\session\ConnectionPropsForSessCfg.ts + +prototypes\packages\cli\src\zosfiles\list\ds\DataSet.handler.ts