diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index c7b2951d..00000000 --- a/.travis.yml +++ /dev/null @@ -1,21 +0,0 @@ ---- -language: java -addons: - apt: - packages: - # http://packages.ubuntu.com/precise/ping - # Commented out inetutils-ping because it's not in the whitelist - # https://github.com/travis-ci/apt-package-whitelist/pull/966 - #- inetutils-ping -# http://stackoverflow.com/questions/14694139/how-to-resolve-dependencies-between-modules-within-multi-module-project -install: mvn compile org.apache.maven.plugins:maven-dependency-plugin:2.10:go-offline --batch-mode --show-version -script: - # add our bin folder to the PATH so our fake ping will be picked up when running under Travis CI - # Inspiration: https://github.com/nono/cozy-desktop/commit/26ab9df277d1cbf781e9e476d022988ab0113154 - - export PATH="bin:$PATH" - # We can't add '--offline' below because the dependency plugin misses some of the dependencies - - mvn clean verify --batch-mode -# https://docs.travis-ci.com/user/migrating-from-legacy/ -sudo: false -jdk: - - oraclejdk8 diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..f9ba8cf6 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,9 @@ +# Microsoft Open Source Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). + +Resources: + +- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) +- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) +- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..5f5d075a --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,10 @@ +# Contributing + +This project welcomes contributions and suggestions. Most contributions require you to +agree to a Contributor License Agreement (CLA) declaring that you have the right to, +and actually do, grant us the rights to use your contribution. For details, visit +https://cla.microsoft.com. + +When you submit a pull request, a CLA-bot will automatically determine whether you need +to provide a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the +instructions provided by the bot. You will only need to do this once across all repositories using our CLA. diff --git a/NOTICE.txt b/NOTICE.txt new file mode 100644 index 00000000..4839d53b --- /dev/null +++ b/NOTICE.txt @@ -0,0 +1,7 @@ +NOTICES + +This repository incorporates material as listed below or described in the code. + +This product includes software from https://github.com/microsoft/vsts-authentication-library-for-java. +Copyright (c) Microsoft. All rights reserved. +Licensed under the MIT license. diff --git a/README.md b/README.md index 6148f7c5..de082a98 100644 --- a/README.md +++ b/README.md @@ -1,77 +1,42 @@ -# Visual Studio Team Services Authentication Library for Java (Preview) [![Build Status](https://travis-ci.org/Microsoft/vsts-authentication-library-for-java.svg?branch=master)](https://travis-ci.org/Microsoft/vsts-authentication-library-for-java) -Retrieve OAuth2 Access Token or Personal Accesss Tokens for Visual Studio Team Services (visualstudio.com) accounts. Also provides secure storage for those secrets on different platforms. +# Credential Secure Storage for Java +Unified interface to store Java application secrets on different platforms. -To learn more about Visual Studio Team Services and our Java specific tools, please visit https://java.visualstudio.com. +The library is derivative work from [Visual Studio Team Services Authentication Library for Java (Preview)](https://github.com/microsoft/vsts-authentication-library-for-java), +`auth-secure-storage` module in particular, focusing on secure storage only. -What this library provides --------------------------- -This library provides: - -1. a set of `authenticators` in the `core` module that can be used to retrieve credentials in the form of OAuth2 Access Token or Personal Access Token against any Visual Studio Team Services account. -1. a set of secure `storage` providers that store retrieved secrets, as well as In memory and File system backed insecure storages. -1. a set of `providers` that hide the interaction between `storage` and `authenticator`, and returns authenticated `client` that can be used directly against Visual Studio Team Services REST APIs. +# What this library provides +This library provides a set of secure `storage` providers that store retrieved secrets, as well as In memory and File system backed insecure storages. ### Available Secure Storage Providers: +| Secret Type | Windows (Credential Manager) | Linux (GNOME Keyring v2.22+) | Mac OSX (Keychain)| +|------------------------------------------------|------------------------------|-------------------------------|-------------------| +| Username / Password Credentials (`Credential`) | Yes | Yes | Yes | +| OAuth2 Access/Refresh Token (`TokenPair`) | Yes (On Windows 7, 8/8.1 and 10) | Yes | Yes | +| Personal Access Token (`Token`) | Yes | Yes | Yes | -| Secret Type | Windows (Credential Manager) | Linux (GNOME Keyring v2.22+) | Mac OSX (Keychain)| -|--------------------------|------------------------|-------------------------|-------------------------| -| Username / Password Combo (`Credential`) | Yes | Yes | Yes | -| OAuth2 Access/Refresh Token (`TokenPair`) | Yes (On Windows 7, 8/8.1 and 10) | Yes | Yes | -| VSTS Personal Access Token (`Token`) | Yes | Yes | Yes | - - -How to use this library ------------------------ - +# How to use this library Maven is the preferred way to referencing this library. ```xml - com.microsoft.alm - auth-providers -    0.6.4 - -``` - -If only interested in specific modules: - -```xml - - com.microsoft.alm - auth-secure-storage -    0.6.4 + com.microsoft.a4o + credential-secure-storage + 1.0.0 ``` -```xml - - com.microsoft.alm - auth-core -    0.6.4 - -``` - -Here is a [Sample App](sample/src/main/java/com/microsoft/alm/auth/sample/App.java) that uses this library. +Here is sample code for [credentials](sample/src/main/java/com/microsoft/a4o/credentialstorage/sample/AppCredential.java) +and [tokens](sample/src/main/java/com/microsoft/a4o/credentialstorage/sample/AppToken.java) that shows how to use this library. -How to build ------------- +# How to build 1. JDK 11 2. Maven 3.8+ 3. `mvn clean verify` - -How can I contribute? ---------------------- -This is a preview release, please open issues and give us feedback! We also welcome Pull Requests. - - -License -------- +# License The MIT license can be found in [LICENSE.txt](LICENSE.txt) +See the [NOTICE.txt](NOTICE.txt) file for required notices and attributions. - -Code of Conduct ---------------- -This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. - +# Trademarks +This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow Microsoft’s Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party’s policies. diff --git a/SECURITY.MD b/SECURITY.MD new file mode 100644 index 00000000..a39e7812 --- /dev/null +++ b/SECURITY.MD @@ -0,0 +1,37 @@ +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)), please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + +* Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) +* Full paths of source file(s) related to the manifestation of the issue +* The location of the affected source code (tag/branch/commit or direct URL) +* Any special configuration required to reproduce the issue +* Step-by-step instructions to reproduce the issue +* Proof-of-concept or exploit code (if possible) +* Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). diff --git a/ThirdPartyNotices.txt b/ThirdPartyNotices.txt deleted file mode 100644 index db8664c4..00000000 --- a/ThirdPartyNotices.txt +++ /dev/null @@ -1,72 +0,0 @@ - -THIRD-PARTY SOFTWARE NOTICES AND INFORMATION -Do Not Translate or Localize - -Visual Studio Team Services Authentication Library for Java - - - -This project is based on or incorporates material from the projects listed below (Third Party Code). The original copyright notice and the license under which Microsoft received such Third Party Code, are set forth below. Such licenses and notices are provided for informational purposes only. Microsoft licenses the Third Party Code to you under the licensing terms for the Microsoft product. Microsoft reserves all other rights not expressly granted under this agreement, whether by implication, estoppel or otherwise. - - -1. org.eclipse.swt.browser (http://archive.eclipse.org/eclipse/downloads/drops4/R-4.4.2-201502041700/) - - -%% org.eclipse.swt.browser NOTICES AND INFORMATION BEGIN HERE -========================================= -You may obtain the source code for org.eclipse.swt.browser from us, if and as required under the relevant open source license, for a period of one year after our last shipment of this product, by sending a money order or check for $5.00 to: Source Code Compliance Team, Microsoft Corporation, 1 Microsoft Way, Redmond, WA 98052. Please write “source code for Visual Studio Team Services Authentication Library for Java” in the memo line of your payment. We may also make a copy of the source code available at http://thirdpartysource.microsoft.com. - -All past Contributors to the Third Party Code licensed under the Eclipse Public License disclaim all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose. In addition, such Contributors are not liable for any damages, including direct, indirect, special, incidental and consequential damages, such as lost profits. Any provisions of the licensing terms of the Microsoft product that differ from the Eclipse Public License are offered by Microsoft alone and not by any other party. - -Provided for Informational Purposes Only - -Eclipse Public License - v 1.0 -THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. -1. DEFINITIONS -"Contribution" means: -a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and -b) in the case of each subsequent Contributor: -i) changes to the Program, and -ii) additions to the Program; -where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. -"Contributor" means any person or entity that distributes the Program. -"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. -"Program" means the Contributions distributed in accordance with this Agreement. -"Recipient" means anyone who receives the Program under this Agreement, including all Contributors. -2. GRANT OF RIGHTS -a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. -b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. -c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. -d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. -3. REQUIREMENTS -A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: -a) it complies with the terms and conditions of this Agreement; and -b) its license agreement: -i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; -ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; -iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and -iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. -When the Program is made available in source code form: -a) it must be made available under this Agreement; and -b) a copy of this Agreement must be included with each copy of the Program. -Contributors may not remove or alter any copyright notices contained within the Program. -Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. -4. COMMERCIAL DISTRIBUTION -Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. -For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. -5. NO WARRANTY -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. -6. DISCLAIMER OF LIABILITY -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -7. GENERAL -If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. -If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. -All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. -Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. -This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. -========================================= -END OF org.eclipse.swt.browser NOTICES AND INFORMATION - - - - diff --git a/common/pom.xml b/common/pom.xml deleted file mode 100644 index 94e48440..00000000 --- a/common/pom.xml +++ /dev/null @@ -1,125 +0,0 @@ - - - 4.0.0 - - com.microsoft.alm - auth-lib-parent - 0.6.5 - - auth-common - jar - - Common interfaces, helpers and data models classes for Java Authentication Library - Collection of data models and helpers for authenticating against VSTS and TFS - https://java.visualstudio.com/ - - - - - ${basedir}/src/main/resources - ./ - - - ${basedir}/.. - ./ - false - - LICENSE.txt - - - - - - maven-enforcer-plugin - - - enforce-versions - - enforce - - - - - 11 - - - - - - - - maven-compiler-plugin - - 11 - 11 - - - - org.codehaus.gmavenplus - gmavenplus-plugin - - - org.apache.maven.plugins - maven-javadoc-plugin - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - - - - - javax.xml.bind - jaxb-api - 2.3.1 - provided - - - - org.slf4j - slf4j-api - - - - org.slf4j - slf4j-nop - test - - - - junit - junit - test - - - org.mockito - mockito-core - test - - - org.codehaus.groovy - groovy - test - - - - diff --git a/common/src/main/java/com/microsoft/alm/helpers/Action.java b/common/src/main/java/com/microsoft/alm/helpers/Action.java deleted file mode 100644 index 727c031c..00000000 --- a/common/src/main/java/com/microsoft/alm/helpers/Action.java +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -/** - * Represents a method that accepts one argument. - * - * @param the type of the input to the method - */ -public interface Action { - /** - * Calls the method with the given argument - * - * @param t the method argument - */ - void call(final T t); -} diff --git a/common/src/main/java/com/microsoft/alm/helpers/BitConverter.java b/common/src/main/java/com/microsoft/alm/helpers/BitConverter.java deleted file mode 100644 index d9d4dc2c..00000000 --- a/common/src/main/java/com/microsoft/alm/helpers/BitConverter.java +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -/** - * Equivalent to .NET's BitConverter class - */ -public class BitConverter { - /** - * Converts the numeric value of each element of a specified array of bytes - * to its equivalent hexadecimal string representation. - * - * @param bytes An array of bytes. - * @return A string of hexadecimal pairs separated by hyphens, - * where each pair represents the corresponding element in bytes; - * for example, "7F-2C-4A-00". - */ - public static String toString(final byte[] bytes) { - final StringBuilder sb = new StringBuilder(); - boolean first = true; - for (final byte b : bytes) { - if (!first) { - sb.append('-'); - } - first = false; - final String pair = String.format("%1$X", b); - if (pair.length() < 2) { - sb.append('0'); - } - sb.append(pair); - } - return sb.toString(); - } -} diff --git a/common/src/main/java/com/microsoft/alm/helpers/Debug.java b/common/src/main/java/com/microsoft/alm/helpers/Debug.java deleted file mode 100644 index dfe22ba0..00000000 --- a/common/src/main/java/com/microsoft/alm/helpers/Debug.java +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -import javax.swing.JFrame; -import javax.swing.JOptionPane; - -public class Debug { - private static final String ASSERTION_FAILED_TITLE = "Assertion Failed: Yes=Quit, No=Continue"; - public static final boolean IsDebug; - - static { - final String debug = System.getProperty("debug"); - IsDebug = (debug != null && !("0".equals(debug) || "false".equalsIgnoreCase(debug))); - } - - public static void Assert(final boolean condition, final String message) { - if (IsDebug) { - if (!condition) { - // http://stackoverflow.com/a/1069150/ - final Throwable throwable = new Throwable(); - final StackTraceElement[] stackTraceElements = throwable.getStackTrace(); - final StringBuilder sb = new StringBuilder(); - sb.append(message); - sb.append("\n"); - sb.append("\n"); - boolean first = true; - for (final StackTraceElement stackTraceElement : stackTraceElements) { - if (first) { - first = false; - } else { - sb.append(stackTraceElement.toString()); - sb.append("\n"); - } - } - final String fullMessage = sb.toString(); - - // http://stackoverflow.com/a/543012/ - final JFrame frame = new JFrame(ASSERTION_FAILED_TITLE); - final int result; - try { - frame.setUndecorated(true); - frame.setVisible(true); - frame.setLocationRelativeTo(null); - result = JOptionPane.showConfirmDialog( - frame, - fullMessage, - ASSERTION_FAILED_TITLE, - JOptionPane.YES_NO_OPTION, - JOptionPane.ERROR_MESSAGE, - null - ); - } finally { - frame.dispose(); - } - if (JOptionPane.YES_OPTION == result) { - System.exit(1); - } - } - - } - } -} diff --git a/common/src/main/java/com/microsoft/alm/helpers/Environment.java b/common/src/main/java/com/microsoft/alm/helpers/Environment.java deleted file mode 100644 index 50e1e46f..00000000 --- a/common/src/main/java/com/microsoft/alm/helpers/Environment.java +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -import java.io.File; -import java.net.InetAddress; -import java.net.UnknownHostException; - -/** - * Equivalent to the .NET class with the same name. - */ -public class Environment { - public static String getCurrentDirectory() { - // http://stackoverflow.com/a/16802976/ - final File currentDir = new File(""); - final String result = currentDir.getAbsolutePath(); - return result; - } - - public static String getFolderPath(final SpecialFolder folder) { - switch (folder) { - case ApplicationData: - return System.getenv("APPDATA"); - case LocalApplicationData: - return System.getenv("LOCALAPPDATA"); - case UserProfile: - return System.getProperty("user.home"); - default: - throw new IllegalArgumentException("Very few SpecialFolder flags are supported."); - } - } - - public static String getMachineName() { - String machineName = null; - if (machineName == null) { - machineName = System.getenv("COMPUTERNAME"); - } - if (machineName == null) { - machineName = System.getenv("HOSTNAME"); - } - if (machineName == null) { - try { - final InetAddress address = InetAddress.getLocalHost(); - machineName = address.getHostName(); - } catch (final UnknownHostException e) { - machineName = "unknown"; - } - } - return machineName; - } - - public static final String NewLine = System.getProperty("line.separator"); - - public enum SpecialFolder { - Desktop, - Programs, - MyDocuments, - Personal, - Favorites, - Startup, - Recent, - SendTo, - StartMenu, - MyMusic, - MyVideos, - DesktopDirectory, - MyComputer, - NetworkShortcuts, - Fonts, - Templates, - CommonStartMenu, - CommonPrograms, - CommonStartup, - CommonDesktopDirectory, - /** - * The directory that serves as a common repository for application-specific data - * for the current roaming user. - *

- * A roaming user works on more than one computer on a network. - * A roaming user's profile is kept on a server on the network and - * is loaded onto a system when the user logs on. - */ - ApplicationData, - PrinterShortcuts, - /** - * The directory that serves as a common repository for application-specific data - * that is used by the current, non-roaming user. - */ - LocalApplicationData, - InternetCache, - Cookies, - History, - CommonApplicationData, - Windows, - System, - ProgramFiles, - MyPictures, - /** - * The user's profile folder. - * Applications should not create files or folders at this level; - * they should put their data under the locations referred to by {@link #ApplicationData}. - */ - UserProfile, - SystemX86, - ProgramFilesX86, - CommonProgramFiles, - CommonProgramFilesX86, - CommonTemplates, - CommonDocuments, - CommonAdminTools, - AdminTools, - CommonMusic, - CommonPictures, - CommonVideos, - Resources, - LocalizedResources, - CommonOemLinks, - CDBurning, - } -} diff --git a/common/src/main/java/com/microsoft/alm/helpers/Func.java b/common/src/main/java/com/microsoft/alm/helpers/Func.java deleted file mode 100644 index 9e1e80b1..00000000 --- a/common/src/main/java/com/microsoft/alm/helpers/Func.java +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -/** - * Represents a function that accepts one argument and produces a result. - * - * @param the type of the input to the function - * @param the type of the result of the function - */ -public interface Func { - /** - * Calls the function with the given argument. - * - * @param t the function argument - * @return the function result - */ - R call(T t); -} diff --git a/common/src/main/java/com/microsoft/alm/helpers/Guid.java b/common/src/main/java/com/microsoft/alm/helpers/Guid.java deleted file mode 100644 index 601d732d..00000000 --- a/common/src/main/java/com/microsoft/alm/helpers/Guid.java +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -import java.nio.ByteBuffer; -import java.nio.ByteOrder; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicReference; - -public class Guid { - public static final UUID Empty = UUID.fromString("00000000-0000-0000-0000-000000000000"); - - // http://stackoverflow.com/a/28628209/ - public static UUID fromBytes(final byte[] b) { - ByteBuffer source = ByteBuffer.wrap(b); - ByteBuffer target = ByteBuffer.allocate(16). - order(ByteOrder.LITTLE_ENDIAN). - putInt(source.getInt()). - putShort(source.getShort()). - putShort(source.getShort()). - order(ByteOrder.BIG_ENDIAN). - putLong(source.getLong()); - target.rewind(); - return new UUID(target.getLong(), target.getLong()); - } - - // Inspired by: http://stackoverflow.com/a/1055668/ - public static byte[] toBytes(final UUID value) { - final ByteBuffer bytes = ByteBuffer.allocate(16); - - final long mostSignificantBits = value.getMostSignificantBits(); - - final int upperMsb = (int) (mostSignificantBits >> 32); - bytes.putInt(Integer.reverseBytes(upperMsb)); - - final int lowerMsb = (int) (mostSignificantBits & 0xffffffff); - final short firstLowerMsb = (short) (lowerMsb >> 16); - bytes.putShort(Short.reverseBytes(firstLowerMsb)); - final short secondLowerMsb = (short) (lowerMsb & 0xffff); - bytes.putShort(Short.reverseBytes(secondLowerMsb)); - - bytes.putLong(value.getLeastSignificantBits()); - - return bytes.array(); - } - - public static boolean tryParse(final String input, final AtomicReference result) { - if (input == null) { - return false; - } - try { - result.set(UUID.fromString(input)); - return true; - } catch (final IllegalArgumentException ignored) { - return false; - } - } -} diff --git a/common/src/main/java/com/microsoft/alm/helpers/HttpClient.java b/common/src/main/java/com/microsoft/alm/helpers/HttpClient.java deleted file mode 100644 index 72e522ed..00000000 --- a/common/src/main/java/com/microsoft/alm/helpers/HttpClient.java +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URI; -import java.util.Map; - -public interface HttpClient { - - /** - * Return a reference to the headers this request will use - * - * Must return a reference, not a clone or a copy - * - * @return the reference of headers will be used for this request - */ - Map getHeaders(); - - /** - * Make a HEAD call and get the header value returned - * - * @param uri target uri - * @param header the header to retrieve - * @return value of the header, null if this header doesn't exist - * @throws IOException - */ - String getHeaderField(URI uri, String header) throws IOException; - - /** - * Read response from a GET HTTP call to the targetUri - * - * @param uri - * @return response - * @throws IOException if response status code is not 2xx, the error message is the error from server. - * stream from the connection - */ - String getGetResponseText(URI uri) throws IOException; - String getGetResponseText(URI uri, int Timeout) throws IOException; - - /** - * Read the response from a POST HTTP call to the target uri - * @param uri - * @param content - * @return response - * @throws IOException if response status code is not 2xx, the error message is the error from server. - */ - String getPostResponseText(URI uri, StringContent content) throws IOException; - - /** - * Read the response from a POST HTTP call, but don't throw any exception even if the call is not successful - * @param uri - * @param content - * @return - * @throws IOException - */ - HttpResponse getPostResponse(URI uri, StringContent content) throws IOException; -} diff --git a/common/src/main/java/com/microsoft/alm/helpers/HttpClientImpl.java b/common/src/main/java/com/microsoft/alm/helpers/HttpClientImpl.java deleted file mode 100644 index b02cb126..00000000 --- a/common/src/main/java/com/microsoft/alm/helpers/HttpClientImpl.java +++ /dev/null @@ -1,211 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.MalformedURLException; -import java.net.ProtocolException; -import java.net.URI; -import java.net.URL; -import java.util.LinkedHashMap; -import java.util.Map; - -public class HttpClientImpl implements HttpClient { - - private static final Logger logger = LoggerFactory.getLogger(HttpClientImpl.class); - - public final Map Headers = new LinkedHashMap(); - - public HttpClientImpl(final String userAgent) { - Headers.put("User-Agent", userAgent); - } - - private void ensureOK(final HttpURLConnection connection) throws IOException { - final int statusCode = connection.getResponseCode(); - if (statusCode != HttpURLConnection.HTTP_OK) { - InputStream errorStream = null; - try { - errorStream = connection.getErrorStream(); - String content = ""; - if (errorStream != null) { - content = IOHelper.readToString(errorStream); - } - final String template = "HTTP request failed with code %1$d: %2$s"; - final String message = String.format(template, statusCode, content); - throw new IOException(message); - } finally { - IOHelper.closeQuietly(errorStream); - } - } - } - - private static String readToString(final HttpURLConnection connection) throws IOException { - return readToString(connection.getInputStream()); - } - - private static String readErrorToString(final HttpURLConnection connection) throws IOException { - return readToString(connection.getErrorStream()); - } - - private static String readToString(final InputStream responseStream) throws IOException { - final String responseContent; - try { - responseContent = IOHelper.readToString(responseStream); - } finally { - IOHelper.closeQuietly(responseStream); - } - return responseContent; - } - - HttpURLConnection createConnection(final URI uri, final String method, final Action interceptor) { - final URL url; - try { - url = uri.toURL(); - } catch (final MalformedURLException e) { - throw new Error(e); - } - - final HttpURLConnection connection; - try { - connection = (HttpURLConnection) url.openConnection(); - } catch (final IOException e) { - throw new Error(e); - } - - try { - connection.setRequestMethod(method); - } catch (final ProtocolException e) { - throw new Error(e); - } - - for (final Map.Entry entry : Headers.entrySet()) { - final String key = entry.getKey(); - final String value = entry.getValue(); - connection.setRequestProperty(key, value); - } - - if (interceptor != null) { - interceptor.call(connection); - } - - return connection; - } - - private HttpURLConnection head(final URI uri) throws IOException { - return head(uri, null); - } - - private HttpURLConnection head(final URI uri, final Action interceptor) throws IOException { - final HttpURLConnection connection = createConnection(uri, "HEAD", interceptor); - connection.connect(); - - return connection; - } - - @Override - public Map getHeaders() { - return Headers; - } - - @Override - public String getHeaderField(final URI uri, final String header) throws IOException { - return getHeaderField(uri, header, new Action() { - @Override - public void call(final HttpURLConnection conn) { - conn.setInstanceFollowRedirects(false); - } - }); - } - - private String getHeaderField(URI uri, String header, Action interceptor) throws IOException { - final HttpURLConnection connection = this.head(uri, interceptor); - - return connection.getHeaderField(header); - } - - private HttpURLConnection get(final URI uri) throws IOException { - return get(uri, null); - } - - private HttpURLConnection get(final URI uri, final Action interceptor) throws IOException { - final HttpURLConnection connection = createConnection(uri, "GET", interceptor); - connection.setDoInput(true); - - return connection; - } - - @Override - public String getGetResponseText(URI uri) throws IOException { - final HttpURLConnection response = this.get(uri); - this.ensureOK(response); - - return readToString(response); - } - - @Override - public String getGetResponseText(URI uri, final int timeout) throws IOException { - final HttpURLConnection response = this.get(uri, new Action() { - @Override - public void call(HttpURLConnection httpURLConnection) { - httpURLConnection.setConnectTimeout(timeout); - } - }); - this.ensureOK(response); - - return readToString(response); - } - - private HttpURLConnection post(final URI uri, final StringContent content) throws IOException { - return post(uri, content, new Action() { - @Override - public void call(final HttpURLConnection conn) { - conn.setUseCaches(false); - } - }); - } - - private HttpURLConnection post(final URI uri, final StringContent content, final Action interceptor) throws IOException { - final HttpURLConnection connection = createConnection(uri, "POST", interceptor); - connection.setDoInput(true); - connection.setDoOutput(true); - - content.write(connection); - - return connection; - } - - @Override - public String getPostResponseText(URI uri, StringContent content) throws IOException { - final HttpURLConnection response = this.post(uri, content); - this.ensureOK(response); - - return readToString(response); - } - - @Override - public HttpResponse getPostResponse(URI uri, StringContent content) throws IOException { - final HttpResponse response = new HttpResponse(); - final HttpURLConnection conn = this.post(uri, content); - - response.status = conn.getResponseCode(); - if (isSuccessful(response.status)) { - response.responseText = readToString(conn); - } else { - response.errorText = readErrorToString(conn); - } - - return response; - } - - private boolean isSuccessful(final int statusCode) { - // https://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html - // 2xx successful - return statusCode > 199 && statusCode < 300; - } -} diff --git a/common/src/main/java/com/microsoft/alm/helpers/HttpResponse.java b/common/src/main/java/com/microsoft/alm/helpers/HttpResponse.java deleted file mode 100644 index 9aa9c561..00000000 --- a/common/src/main/java/com/microsoft/alm/helpers/HttpResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -/** - * Struct class to carry http call responses - */ -public class HttpResponse { - public int status; - public String responseText; - public String errorText; -} diff --git a/common/src/main/java/com/microsoft/alm/helpers/IOHelper.java b/common/src/main/java/com/microsoft/alm/helpers/IOHelper.java deleted file mode 100644 index e6b05f48..00000000 --- a/common/src/main/java/com/microsoft/alm/helpers/IOHelper.java +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -import java.io.BufferedReader; -import java.io.Closeable; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; - -public class IOHelper { - - static final int BUFFER_SIZE = 4096; - - public static void closeQuietly(final Closeable closeable) { - if (closeable != null) { - try { - closeable.close(); - } catch (final IOException ignored) { - } - } - } - - public static String readFileToString(final File file) throws IOException { - FileInputStream fis = null; - try { - fis = new FileInputStream(file); - return readToString(fis); - } finally { - IOHelper.closeQuietly(fis); - } - } - - public static String readToString(final InputStream stream) throws IOException { - InputStreamReader isr = null; - BufferedReader reader = null; - try { - isr = new InputStreamReader(stream); - reader = new BufferedReader(isr); - - final StringBuilder sb = new StringBuilder(); - String line; - while ((line = reader.readLine()) != null) { - sb.append(line); - sb.append(Environment.NewLine); - } - return sb.toString(); - } finally { - IOHelper.closeQuietly(reader); - IOHelper.closeQuietly(isr); - } - } - - public static void copyStream(final InputStream is, final OutputStream os) throws IOException { - final byte[] buffer = new byte[BUFFER_SIZE]; - int bytesRead; - while ((bytesRead = is.read(buffer, 0, BUFFER_SIZE)) != -1) { - os.write(buffer, 0, bytesRead); - } - os.flush(); - } -} diff --git a/common/src/main/java/com/microsoft/alm/helpers/IteratorExtensions.java b/common/src/main/java/com/microsoft/alm/helpers/IteratorExtensions.java deleted file mode 100644 index 2aa2a9f8..00000000 --- a/common/src/main/java/com/microsoft/alm/helpers/IteratorExtensions.java +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -import java.util.Iterator; - -public class IteratorExtensions { - public static T firstOrDefault(final Iterator iterator) { - if (iterator.hasNext()) { - return iterator.next(); - } else { - return null; - } - } -} diff --git a/common/src/main/java/com/microsoft/alm/helpers/LoggingHelper.java b/common/src/main/java/com/microsoft/alm/helpers/LoggingHelper.java deleted file mode 100644 index 46b800b8..00000000 --- a/common/src/main/java/com/microsoft/alm/helpers/LoggingHelper.java +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -import org.slf4j.Logger; - -public class LoggingHelper { - - /** - * IntelliJ bubbles up all error level logging to user, and if there is a "cause", it makes it a clickable link - * and user can view the stacktrace. - * - * However, IntelliJ also exposes an button to disable this plugin on the stacktrace viewer, which is not - * desirable, so in this case we just log the error message, but show the cause in a warning log - * - * @param logger the logger to use - * @param message the message to display - * @param cause the chained exception - */ - public static void logError(final Logger logger , final String message, final Throwable cause) { - logger.error(message); - //weird thing we are doing for IntelliJ - logger.warn(message, cause); - } -} diff --git a/common/src/main/java/com/microsoft/alm/helpers/NotImplementedException.java b/common/src/main/java/com/microsoft/alm/helpers/NotImplementedException.java deleted file mode 100644 index 0fbfd351..00000000 --- a/common/src/main/java/com/microsoft/alm/helpers/NotImplementedException.java +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -public class NotImplementedException extends RuntimeException { - private final int workItemNumber; - private final String details; - - public NotImplementedException(final int workItemNumber) { - this(workItemNumber, null); - } - - public NotImplementedException(final int workItemNumber, final String details) { - super("This feature is not yet implemented, but is tracked by work item #" + workItemNumber + ((details != null) ? ". " + details : ".")); - this.workItemNumber = workItemNumber; - this.details = details; - } - - public int getWorkItemNumber() { - return workItemNumber; - } - - public String getDetails() { - return details; - } -} diff --git a/common/src/main/java/com/microsoft/alm/helpers/ObjectExtensions.java b/common/src/main/java/com/microsoft/alm/helpers/ObjectExtensions.java deleted file mode 100644 index 55bfd76e..00000000 --- a/common/src/main/java/com/microsoft/alm/helpers/ObjectExtensions.java +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -public class ObjectExtensions { - /** - * Equivalent to the C# null-coalescing operator '??'. - * - * @param the type of both values. - * @param maybeNullValue the value that might be null. - * @param nonNullValue the value to use if the other one is null. - * @return maybeNullValue if maybeNullValue is not null; otherwise it returns nonNullValue. - */ - public static T coalesce(final T maybeNullValue, final T nonNullValue) { - return maybeNullValue == null ? nonNullValue : maybeNullValue; - } -} diff --git a/common/src/main/java/com/microsoft/alm/helpers/Path.java b/common/src/main/java/com/microsoft/alm/helpers/Path.java deleted file mode 100644 index 550b0fee..00000000 --- a/common/src/main/java/com/microsoft/alm/helpers/Path.java +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -import java.io.File; - -/** - * Equivalent to System.IO.Path - */ -public class Path { - public static String changeExtension(final String path, final String extension) { - if (StringHelper.isNullOrEmpty(path)) { - return path; - } - final StringBuilder sb = new StringBuilder(path.length()); - final String minusLastExtension = getFileNameWithoutExtension(path); - sb.append(minusLastExtension); - - if (extension != null) { - if (!extension.startsWith(".") && !path.endsWith(".")) { - sb.append('.'); - } - sb.append(extension); - } - return sb.toString(); - } - - public static String combine(final String path1, final String path2) { - final File file = new File(path1, path2); - return file.getPath(); - } - - public static boolean directoryExists(final String path) { - final File file = new File(path); - return file.exists() && file.isDirectory(); - } - - public static boolean fileExists(final String path) { - final File file = new File(path); - return file.exists() && file.isFile(); - } - - public static String getDirectoryName(final String path) { - final File file = new File(path); - return file.getParent(); - } - - // Inspired by http://stackoverflow.com/a/4094034 - public static String getFileNameWithoutExtension(final String path) { - final String result = path.replaceAll("^(.*[/\\\\]?[^/\\\\]*)(\\.[^./\\\\]+)$", "$1"); - return result; - } - - public static boolean isAbsolute(final String path) { - final File file = new File(path); - return file.isAbsolute(); - } - - public static String construct(final String... segments) { - final StringBuilder builder = new StringBuilder(); - for (final String segment : segments) { - builder.append(segment).append(File.separator); - } - // Remove last trailing File.separator - final int idx = builder.lastIndexOf(File.separator); - if (idx > -1) { - builder.deleteCharAt(builder.lastIndexOf(File.separator)); - } - - return builder.toString(); - } -} diff --git a/common/src/main/java/com/microsoft/alm/helpers/PropertyBag.java b/common/src/main/java/com/microsoft/alm/helpers/PropertyBag.java deleted file mode 100644 index f4724980..00000000 --- a/common/src/main/java/com/microsoft/alm/helpers/PropertyBag.java +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -import java.util.LinkedHashMap; - -public class PropertyBag extends LinkedHashMap { - - public static PropertyBag fromJson(final String input) { - final PropertyBag result = new PropertyBag(); - SimpleJson.parse(input, result); - return result; - } - - public int readOptionalInteger(final String key, final int defaultValue) { - final int result; - if (containsKey(key)) { - final Object candidateResult = get(key); - if (candidateResult instanceof Double) { - final Double resultAsDouble = (Double) candidateResult; - result = (int) Math.round(resultAsDouble); - } - else if (candidateResult instanceof String) { - final String resultAsString = (String) candidateResult; - result = Integer.parseInt(resultAsString, 10); - } - else { - result = defaultValue; - } - } - else { - result = defaultValue; - } - - return result; - } - - public String readOptionalString(final String key, final String defaultValue) { - final String result; - if (containsKey(key)) { - result = (String) get(key); - } - else { - result = defaultValue; - } - return result; - } -} diff --git a/common/src/main/java/com/microsoft/alm/helpers/QueryString.java b/common/src/main/java/com/microsoft/alm/helpers/QueryString.java deleted file mode 100644 index f1712025..00000000 --- a/common/src/main/java/com/microsoft/alm/helpers/QueryString.java +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -import java.util.LinkedHashMap; - -public class QueryString extends LinkedHashMap { - @Override - public String toString() { - return UriHelper.serializeParameters(this); - } -} diff --git a/common/src/main/java/com/microsoft/alm/helpers/ScopeSet.java b/common/src/main/java/com/microsoft/alm/helpers/ScopeSet.java deleted file mode 100644 index ecbd7694..00000000 --- a/common/src/main/java/com/microsoft/alm/helpers/ScopeSet.java +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -import java.util.Arrays; -import java.util.TreeSet; - -public class ScopeSet extends TreeSet { - public void unionWith(final String[] items) { - this.addAll(Arrays.asList(items)); - } - - public void intersectWith(final String[] items) { - this.retainAll(Arrays.asList(items)); - } - - public boolean setEquals(final String[] items) { - return this.size() == items.length - && this.containsAll(Arrays.asList(items)); - } -} diff --git a/common/src/main/java/com/microsoft/alm/helpers/SettingsHelper.java b/common/src/main/java/com/microsoft/alm/helpers/SettingsHelper.java deleted file mode 100644 index 51a06162..00000000 --- a/common/src/main/java/com/microsoft/alm/helpers/SettingsHelper.java +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.FileReader; -import java.io.IOException; -import java.util.Map; -import java.util.Properties; - -/** - * Utility class to read properties from a file. - * - * If the property is not found from the setting file, it will fallback to System properties. - */ -public class SettingsHelper { - - private static final Logger logger = LoggerFactory.getLogger(SettingsHelper.class); - - private static final String VENDOR_FOLDER = SystemHelper.isLinux() ? ".microsoft" : "Microsoft"; - private static final String PROGRAM_FOLDER = "VstsAuthLib4J"; - private static final String FILE_NAME = "settings.properties"; - - private final Properties properties = new Properties(); - - private static final String DO_NOT_SET_SYSTEM_ENV = "doNotSetSystemEnv"; - - private static SettingsHelper instance; - - public static synchronized SettingsHelper getInstance() { - if (instance == null) { - instance = new SettingsHelper(); - } - - return instance; - } - - private static String getSettingsFolderName() { - final String folder; - if (SystemHelper.isWindows()) { - folder = Path.construct(Environment.getFolderPath(Environment.SpecialFolder.LocalApplicationData), - VENDOR_FOLDER, - PROGRAM_FOLDER); - - } else if (SystemHelper.isMac()) { - folder = Path.construct(Environment.getFolderPath(Environment.SpecialFolder.UserProfile), - "Library", - "Application Support", - VENDOR_FOLDER, - PROGRAM_FOLDER); - } else { - folder = Path.construct(Environment.getFolderPath(Environment.SpecialFolder.UserProfile), - VENDOR_FOLDER, - PROGRAM_FOLDER); - - } - - return folder; - } - - private SettingsHelper() { - final String path = getSettingsFolderName(); - final File folder = new File(path); - logger.info("Searching for {}", Path.combine(folder.getAbsolutePath(), FILE_NAME)); - if (folder.exists()) { - final File potential = new File(folder, FILE_NAME); - if (potential.exists() && potential.isFile() && potential.canRead()) { - logger.info("Found setting file, trying to load properties from {}", potential.getAbsolutePath()); - - try { - properties.load(new FileReader(potential)); - logger.info("Properties loaded."); - final boolean setSystemEnv = !(Boolean.valueOf(properties.getProperty(DO_NOT_SET_SYSTEM_ENV))); - if (setSystemEnv) { - // oauth2-useragent reads System properties. If we want to propagate any values downstream, - // we must load our properties into System.properties - for (final Map.Entry entry : properties.entrySet()) { - if (entry.getKey() instanceof String && entry.getValue() instanceof String) { - logger.info("Setting System property {} to {}", entry.getKey(), entry.getValue()); - System.setProperty(entry.getKey().toString(), entry.getValue().toString()); - } - } - } - } catch (Throwable t) { - logger.warn("Failed to load properties.", t); - properties.clear(); - } - } - } - } - - /** - * Get named property. Values from the setting files take precedence over System properties. - * The method returns {@code null} if the property is not defined. - * - * @param name Property name - * @return value of the property. {@code null} if property is not defined. - */ - public synchronized String getProperty(final String name) { - String result = properties.getProperty(name); - if (result == null) { - result = System.getProperty(name); - } - - return result; - } - - - /** - * Get named property. Values from the setting files take precedence over System properties. - * - * If the property is not defined, return the default value. - * - * @param name Property name - * @param defaultValue This value will be returned if property is not defined - * @return value of the property, or the defaultValue. - */ - public synchronized String getProperty(final String name, final String defaultValue) { - final String result = getProperty(name); - return result == null ? defaultValue : result; - } -} diff --git a/common/src/main/java/com/microsoft/alm/helpers/SimpleJson.java b/common/src/main/java/com/microsoft/alm/helpers/SimpleJson.java deleted file mode 100644 index 9b67385a..00000000 --- a/common/src/main/java/com/microsoft/alm/helpers/SimpleJson.java +++ /dev/null @@ -1,354 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * A very simple JSON [de-]serializer that only handles a dictionary of scalars - * (string, number, true, false, null). - */ -public class SimpleJson { - - enum State { - START, - PRE_KEY, - KEY, - PRE_VALUE, - VALUE, - NUMBER_VALUE, - STRING_VALUE, - STRING_VALUE_ESCAPE, - STRING_VALUE_UNICODE, - SQUARE_BRACKET_STRING, - LITERAL_VALUE, - POST_VALUE, - END, - ; - } - - private SimpleJson() { - // not intended to be used as an instance class - } - - static void error(final char c, final State s) { - final String message = "Unexpected character '" + c + "' at state " + s + "."; - throw new IllegalArgumentException(message); - } - - static boolean isDigit(final char c) { - return Character.isDigit(c); - } - - static boolean isHexDigit(final char c) { - return Character.digit(c, 16) != -1; - } - - static boolean isInsignificantWhitespace(final char c) { - return c == ' ' || c == '\n' || c == '\t' || c == '\r'; - } - - static boolean isLeftCurlyBracket(final char c) { - return c == '{'; - } - - static boolean isRightCurlyBracket(final char c) { - return c == '}'; - } - - static boolean isLeftSquareBracket(final char c) { - return c == '['; - } - - static boolean isRightSquareBracket(final char c) { - return c == ']'; - } - - static boolean isColon(final char c) { - return c == ':'; - } - - static boolean isComma(final char c) { - return c == ','; - } - - static boolean isPeriod(final char c) { - return c == '.'; - } - - static boolean isDoubleQuote(final char c) { - return c == '"'; - } - - static boolean isLiteralStart(final char c) { - return c == 't' || c == 'f' || c == 'n'; - } - - static boolean isMinus(final char c) { - return c == '-'; - } - - static boolean isExp(final char c) { - return c == 'e' || c == 'E' || c == '-' || c == '+'; - } - - static boolean isEscape(final char c) { - return c == '\\'; - } - - static Object decodeLiteral(final String input) { - if ("true".equals(input)) { - return true; - } - else if ("false".equals(input)) { - return false; - } - else if ("null".equals(input)) { - return null; - } - throw new IllegalArgumentException("Invalid literal '" + input + "'. Expected one of 'true', 'false' or 'null'."); - } - - public static Map parse(final String input) { - final Map result = new LinkedHashMap(); - parse(input, result); - - return result; - } - - static void parse(final String input, final Map destination) { - final StringBuilder token = new StringBuilder(); - final StringBuilder unicodeHex = new StringBuilder(); - - State state = State.START; - String key = null; - Object value; - for (final char c : input.toCharArray()) { - switch (state) { - case START: - if (isLeftCurlyBracket(c)) { - state = State.PRE_KEY; - } - else if (isInsignificantWhitespace(c)) { - continue; - } - else { - error(c, state); - } - break; - case PRE_KEY: - if (isDoubleQuote(c)) { - state = State.KEY; - } - else if (isRightCurlyBracket(c)) { - state = State.END; - } - else if (isInsignificantWhitespace(c)) { - continue; - } - else { - error(c, state); - } - break; - case KEY: - if (isDoubleQuote(c)) { - key = token.toString(); - token.setLength(0); - state = State.PRE_VALUE; - } - else { - token.append(c); - } - break; - case PRE_VALUE: - if (isColon(c)) { - state = State.VALUE; - } - else if (isInsignificantWhitespace(c)) { - continue; - } - else { - error(c, state); - } - break; - case VALUE: - if (isDoubleQuote(c)) { - state = State.STRING_VALUE; - } - else if (isMinus(c) || isDigit(c)) { - token.append(c); - state = State.NUMBER_VALUE; - } - else if (isLiteralStart(c)) { - token.append(c); - state = State.LITERAL_VALUE; - } - else if (isLeftSquareBracket(c)) { - state = State.SQUARE_BRACKET_STRING; - } - else if (isInsignificantWhitespace(c)) { - continue; - } - else { - error(c, state); - } - break; - case NUMBER_VALUE: - if (isComma(c) || isInsignificantWhitespace(c) || isRightCurlyBracket(c)) { - final String candidateDouble = token.toString(); - token.setLength(0); - value = Double.parseDouble(candidateDouble); - destination.put(key, value); - if (isComma(c)) { - state = State.PRE_KEY; - } - else if (isRightCurlyBracket(c)) { - state = State.END; - } - else { - state = State.POST_VALUE; - } - } - else if (isDigit(c) || isExp(c) || isPeriod(c)) { - token.append(c); - } - else { - error(c, state); - } - break; - case STRING_VALUE: - if (isEscape(c)) { - state = State.STRING_VALUE_ESCAPE; - } - else if (isDoubleQuote(c)) { - value = token.toString(); - token.setLength(0); - destination.put(key, value); - state = State.POST_VALUE; - } - else { - token.append(c); - } - break; - case STRING_VALUE_ESCAPE: - switch (c) { - case '"': - case '\\': - case '/': - token.append(c); - state = State.STRING_VALUE; - break; - case 'b': - token.append('\b'); - state = State.STRING_VALUE; - break; - case 'f': - token.append('\f'); - state = State.STRING_VALUE; - break; - case 'n': - token.append('\n'); - state = State.STRING_VALUE; - break; - case 'r': - token.append('\r'); - state = State.STRING_VALUE; - break; - case 't': - token.append('\t'); - state = State.STRING_VALUE; - break; - case 'u': - state = State.STRING_VALUE_UNICODE; - break; - default: - error(c, state); - } - break; - case STRING_VALUE_UNICODE: - if (isHexDigit(c)) { - unicodeHex.append(c); - if (unicodeHex.length() == 4) { - final String candidateHex = unicodeHex.toString(); - unicodeHex.setLength(0); - final int codePoint = Integer.parseInt(candidateHex, 16); - final char[] chars = Character.toChars(codePoint); - token.append(chars); - state = State.STRING_VALUE; - } - } - else { - error(c, state); - } - break; - case SQUARE_BRACKET_STRING: - if (isRightSquareBracket(c)) { - value = token.toString(); - token.setLength(0); - destination.put(key, value); - state = State.POST_VALUE; - } - else { - token.append(c); - } - break; - case LITERAL_VALUE: - switch (c) { - case 'a': - case 'e': - case 'l': - case 'r': - case 's': - case 'u': - token.append(c); - break; - case ',': - case '}': - final String candidateLiteral = token.toString(); - token.setLength(0); - final Object literal = decodeLiteral(candidateLiteral); - destination.put(key, literal); - if (isComma(c)) { - state = State.POST_VALUE; - } - else { - state = State.END; - } - break; - default: - error(c, state); - } - break; - case POST_VALUE: - if (isComma(c)) { - state = State.PRE_KEY; - } - else if (isRightCurlyBracket(c)) { - state = State.END; - } - else if (isInsignificantWhitespace(c)) { - continue; - } - else { - error(c, state); - } - break; - case END: - if (isInsignificantWhitespace(c)) { - continue; - } - else { - error(c, state); - } - break; - } - } - } - - public static String format(final Map input) { - return null; - } - -} diff --git a/common/src/main/java/com/microsoft/alm/helpers/StringContent.java b/common/src/main/java/com/microsoft/alm/helpers/StringContent.java deleted file mode 100644 index 793856f1..00000000 --- a/common/src/main/java/com/microsoft/alm/helpers/StringContent.java +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -import java.io.IOException; -import java.io.OutputStream; -import java.net.HttpURLConnection; -import java.nio.charset.Charset; -import java.util.LinkedHashMap; -import java.util.Map; - -/** - * Inspired by System.Net.Http.StringContent. - */ -public class StringContent { - private static final String UTF8 = Charset.forName("UTF-8").name(); - private static final String CONTENT_TYPE_TEMPLATE = "%1$s; charset=%2$s"; - - public final Map Headers = new LinkedHashMap(); - - private final String content; - private final byte[] bytes; - - private StringContent(final String content, final String mediaType) { - this.content = content; - this.bytes = StringHelper.UTF8GetBytes(content); - final String contentType = String.format(CONTENT_TYPE_TEMPLATE, mediaType, UTF8); - Headers.put("Content-Type", contentType); - final String contentLength = Integer.toString(this.bytes.length, 10); - Headers.put("Content-Length", contentLength); - } - - public String getContent() { - return this.content; - } - - public static StringContent createUrlEncoded(final QueryString parameters) { - return new StringContent(parameters.toString(), "application/x-www-form-urlencoded"); - } - - public static StringContent createJson(final String json) { - return new StringContent(json, "application/json"); - } - - public void write(final HttpURLConnection connection) throws IOException { - for (final Map.Entry entry : Headers.entrySet()) { - final String key = entry.getKey(); - final String value = entry.getValue(); - connection.setRequestProperty(key, value); - } - OutputStream outputStream = null; - try { - outputStream = connection.getOutputStream(); - outputStream.write(bytes); - } finally { - IOHelper.closeQuietly(outputStream); - } - } -} diff --git a/common/src/main/java/com/microsoft/alm/helpers/StringHelper.java b/common/src/main/java/com/microsoft/alm/helpers/StringHelper.java deleted file mode 100644 index ed071146..00000000 --- a/common/src/main/java/com/microsoft/alm/helpers/StringHelper.java +++ /dev/null @@ -1,256 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -import java.nio.charset.Charset; - -public class StringHelper { - private static final Charset UTF8 = Charset.forName("UTF-8"); - - private static final Charset UTF16LE = Charset.forName("UTF-16LE"); - - public static final String Empty = ""; - - public static boolean endsWithIgnoreCase(final String haystack, final String needle) { - if (haystack == null) - throw new IllegalArgumentException("Parameter 'haystack' is null."); - if (needle == null) - throw new IllegalArgumentException("Parameter 'needle' is null."); - - final int nl = needle.length(); - final int hl = haystack.length(); - if (nl == hl) { - return haystack.equalsIgnoreCase(needle); - } - - if (nl > hl) { - return false; - } - - // Inspired by http://stackoverflow.com/a/19154150/ - final int toffset = hl - nl; - return haystack.regionMatches(true, toffset, needle, 0, nl); - } - - /** - * Reports the zero-based index of the first occurrence in s - * of any character in a specified array of Unicode characters. - * - * @param s The string to search. - * @param anyOf A Unicode character array containing one or more characters to seek. - * @return The zero-based index position of the first occurrence in s - * where any character in anyOf was found; -1 if no character in anyOf was found. - */ - public static int indexOfAny(final String s, final char[] anyOf) { - for (final char c : anyOf) { - final int i = s.indexOf(c); - if (i != -1) { - return i; - } - } - return -1; - } - - public static boolean isNullOrEmpty(final String s) { - return null == s || (s.length() == 0); - } - - public static boolean isNullOrWhiteSpace(final String s) { - return null == s || (s.trim().length() == 0); - } - - /** - * Concatenates the specified elements of a string array, - * using the specified separator between each element. - * - * @param separator The string to use as a separator. - * separator is included in the returned string only if value has more than one element. - * @param value An array that contains the elements to concatenate. - * @return A string that consists of the strings in value delimited by the separator string. - * If value is an empty array, the method returns {@link StringHelper#Empty}. - */ - public static String join(final String separator, final String[] value) { - return join(separator, value, 0, value.length); - } - - /** - * Concatenates the specified elements of a string array, - * using the specified separator between each element. - * - * @param separator The string to use as a separator. - * separator is included in the returned string only if value has more than one element. - * @param value An array that contains the elements to concatenate. - * @param startIndex The first element in value to use. - * @param count The number of elements of value to use. - * @return A string that consists of the strings in value delimited by the separator string. - * -or- - * {@link StringHelper#Empty} if count is zero, value has no elements, - * or separator and all the elements of value are {@link StringHelper#Empty}. - */ - public static String join(final String separator, final String[] value, final int startIndex, final int count) { - return join(separator, value, startIndex, count, null); - } - - /** - * Concatenates the specified elements of a string array, - * using the specified separator between each element. - * - * @param separator The string to use as a separator. - * separator is included in the returned string only if value has more than one element. - * @param value An array that contains the elements to concatenate. - * @param startIndex The first element in value to use. - * @param count The number of elements of value to use. - * @param processor A callback that gets to intercept and modify elements before they are inserted. - * @return A string that consists of the strings in value delimited by the separator string. - * -or- - * {@link StringHelper#Empty} if count is zero, value has no elements, - * or separator and all the elements of value are {@link StringHelper#Empty}. - */ - public static String join(final String separator, final String[] value, final int startIndex, final int count, - final Func processor) { - if (value == null) - throw new IllegalArgumentException("value is null"); - if (startIndex < 0) - throw new IllegalArgumentException("startIndex is less than 0"); - if (count < 0) - throw new IllegalArgumentException("count is less than 0"); - if (startIndex + count > value.length) - throw new IllegalArgumentException("startIndex + count is greater than the number of elements in value"); - - // "If separator is null, an empty string ( String.Empty) is used instead." - final String sep = ObjectExtensions.coalesce(separator, StringHelper.Empty); - - final StringBuilder result = new StringBuilder(); - - if (value.length > 0 && count > 0) { - String element = ObjectExtensions.coalesce(value[startIndex], StringHelper.Empty); - if (processor != null) { - element = processor.call(element); - } - result.append(element); - for (int i = startIndex + 1; i < startIndex + count; i++) { - result.append(sep); - element = ObjectExtensions.coalesce(value[i], StringHelper.Empty); - if (processor != null) { - element = processor.call(element); - } - result.append(element); - } - } - - return result.toString(); - } - - /** - * Removes all trailing occurrences of a set of characters specified in an array from s. - * - * @param s The string to trim from. - * @param trimChars An array of Unicode characters to remove, or null. - * @return The string that remains after all occurrences of the characters in the - * trimChars parameter are removed from the end of s. - * If trimChars is null or an empty array, - * Unicode white-space characters are removed instead. - * If no characters can be trimmed from s, the method returns s. - */ - public static String trimEnd(final String s, final char... trimChars) { - int len = s.length(); - if (trimChars == null || trimChars.length == 0) - return trimEnd(s); - while (len > 0) { - final char current = s.charAt(len - 1); - boolean found = false; - for (final char c : trimChars) { - if (current == c) { - found = true; - break; - } - } - if (!found) - break; - len--; - } - return (len < s.length()) ? s.substring(0, len) : s; - } - - public static String trimEnd(final String s) { - int len = s.length(); - while (len > 0) { - final char current = s.charAt(len - 1); - if (!Character.isWhitespace(current)) - break; - len--; - } - return (len < s.length()) ? s.substring(0, len) : s; - } - - /** - * Encodes all the characters in the specified string into a sequence of UTF-8 bytes. - * - * @param value The string containing the characters to encode. - * @return A byte array containing the results of encoding the specified set of characters. - */ - public static byte[] UTF8GetBytes(final String value) { - final byte[] result = value.getBytes(UTF8); - return result; - } - - /** - * Encodes all the characters in the specified string into a sequence of UTF-16LE bytes. - * - * @param value The string containing the characters to encode. - * @return A byte array containing the results of encoding the specified set of characters. - */ - public static byte[] UTF16LEGetBytes(final String value) { - final byte[] result = value.getBytes(UTF16LE); - return result; - } - - /** - * Decodes all the bytes in the specified byte array into a string. - * - * @param bytes The byte array containing the sequence of bytes to decode. - * @return A string that contains the results of decoding the specified sequence of bytes. - */ - public static String UTF8GetString(final byte[] bytes) { - final String result = new String(bytes, UTF8); - return result; - } - - /** - * Decodes all the bytes in the specified byte array into a string. - * - * @param bytes The byte array containing the sequence of bytes to decode. - * @return A string that contains the results of decoding the specified sequence of bytes. - */ - public static String UTF16LEGetString(final byte[] bytes) { - final String result = new String(bytes, UTF16LE); - return result; - } - - /** - * Decodes a range of bytes from a byte array into a string. - * - * @param bytes The byte array containing the sequence of bytes to decode. - * @param index The index of the first byte to decode. - * @param count The number of bytes to decode. - * @return A string that contains the results of decoding the specified sequence of bytes. - */ - public static String UTF8GetString(final byte[] bytes, final int index, final int count) { - final String result = new String(bytes, index, count, UTF8); - return result; - } - - /** - * Decodes a range of bytes from a byte array into a string. - * - * @param bytes The byte array containing the sequence of bytes to decode. - * @param index The index of the first byte to decode. - * @param count The number of bytes to decode. - * @return A string that contains the results of decoding the specified sequence of bytes. - */ - public static String UTF16LEGetString(final byte[] bytes, final int index, final int count) { - final String result = new String(bytes, index, count, UTF16LE); - return result; - } -} \ No newline at end of file diff --git a/common/src/main/java/com/microsoft/alm/helpers/UriHelper.java b/common/src/main/java/com/microsoft/alm/helpers/UriHelper.java deleted file mode 100644 index 83f8b463..00000000 --- a/common/src/main/java/com/microsoft/alm/helpers/UriHelper.java +++ /dev/null @@ -1,125 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -import java.io.UnsupportedEncodingException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.util.Iterator; -import java.util.Map; -import java.util.regex.Pattern; - -public class UriHelper { - public static final String HOST_AZURE = "azure.com"; - public static final String HOST_AZURE_ORG = ".azure.com"; - - private static final Pattern PAIR_SEPARATOR = Pattern.compile("&"); - private static final Pattern NAME_VALUE_SEPARATOR = Pattern.compile("="); - - public static final String UTF_8 = "UTF-8"; - - public static boolean isWellFormedUriString(final String uriString) { - try { - new URI(uriString); - return true; - } catch (final URISyntaxException ignored) { - return false; - } - } - - public static QueryString deserializeParameters(final String s) { - final QueryString result = new QueryString(); - - if (StringHelper.isNullOrWhiteSpace(s)) { - return result; - } - - final String trimmed = s.trim(); - final String[] pairs = PAIR_SEPARATOR.split(trimmed); - - for (final String pair : pairs) { - final String trimmedPair = pair.trim(); - if (trimmedPair.length() == 0) { - continue; - } - final String[] nameAndValue = NAME_VALUE_SEPARATOR.split(pair, 2); - try { - final String name = URLDecoder.decode(nameAndValue[0], UTF_8); - final String value; - value = nameAndValue.length == 2 ? URLDecoder.decode(nameAndValue[1], UTF_8) : null; - result.put(name, value); - } catch (final UnsupportedEncodingException e) { - throw new Error(e); - } - - } - return result; - } - - public static String serializeParameters(final Map parameters) { - try { - final StringBuilder sb = new StringBuilder(); - final Iterator> iterator = parameters.entrySet().iterator(); - if (iterator.hasNext()) { - Map.Entry entry; - String key; - String encodedKey; - String value; - String encodedValue; - - entry = iterator.next(); - key = entry.getKey(); - encodedKey = URLEncoder.encode(key, UTF_8); - sb.append(encodedKey); - value = entry.getValue(); - if (value != null) { - encodedValue = URLEncoder.encode(value, UTF_8); - sb.append('=').append(encodedValue); - } - while (iterator.hasNext()) { - sb.append('&'); - entry = iterator.next(); - key = entry.getKey(); - encodedKey = URLEncoder.encode(key, UTF_8); - sb.append(encodedKey); - value = entry.getValue(); - if (value != null) { - encodedValue = URLEncoder.encode(value, UTF_8); - sb.append('=').append(encodedValue); - } - } - } - return sb.toString(); - } catch (final UnsupportedEncodingException e) { - throw new Error(e); - } - - } - - public static boolean isAzureHost(final URI uri) { - if ((uri != null && uri.getHost() != null) - && (uri.getHost().equalsIgnoreCase(HOST_AZURE) || StringHelper.endsWithIgnoreCase(uri.getHost(), HOST_AZURE_ORG))) { - return true; - } - return false; - } - - public static String getFullAccount(final URI uri) { - if (isAzureHost(uri)) { - // Get the account which will be in the form azure.com/account - // If we don't have a path with length > 0 check for the mseng@azure.com case - // If we don't match the if or else if, we likely have an issue - String[] paths = uri.getPath().split("/"); - if (paths.length > 1) { - return uri.getHost() + "/" + paths[1]; - } - else if (!StringHelper.isNullOrWhiteSpace(uri.getUserInfo())) { - return uri.getHost() + "/" + uri.getUserInfo(); - } - } - return uri.getHost(); - } -} diff --git a/common/src/main/java/com/microsoft/alm/secret/Credential.java b/common/src/main/java/com/microsoft/alm/secret/Credential.java deleted file mode 100644 index b6940709..00000000 --- a/common/src/main/java/com/microsoft/alm/secret/Credential.java +++ /dev/null @@ -1,167 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.secret; - -import com.microsoft.alm.helpers.ObjectExtensions; -import com.microsoft.alm.helpers.StringHelper; -import com.microsoft.alm.helpers.XmlHelper; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.w3c.dom.Text; - -import javax.xml.bind.DatatypeConverter; -import java.nio.charset.Charset; -import java.util.Map; - -/** - * Credential for user authentication. - */ -public final class Credential extends Secret { - public static final int USERNAME_MAX_LENGTH = 511; - public static final int PASSWORD_MAX_LENGTH = 2047; - - private static final Charset ASCII = Charset.forName("ASCII"); - - public static final Credential Empty = new Credential(StringHelper.Empty, StringHelper.Empty); - - /** - * Creates a credential object with a username and password pair. - * - * @param username The username value of the {@link Credential}. - * @param password The password value of the {@link Credential}. - */ - public Credential(final String username, final String password) { - this.Username = ObjectExtensions.coalesce(username, StringHelper.Empty); - this.Password = ObjectExtensions.coalesce(password, StringHelper.Empty); - } - - /** - * Creates a credential object with only a username. - * - * @param username The username value of the {@link Credential}. - */ - public Credential(final String username) { - this(username, StringHelper.Empty); - } - - /** - * Secret related to the username. - */ - public final String Password; - /** - * Unique identifier of the user. - */ - public final String Username; - - public static Credential fromXml(final Node credentialNode) { - Credential value; - String password = null; - String username = null; - - final NodeList propertyNodes = credentialNode.getChildNodes(); - for (int v = 0; v < propertyNodes.getLength(); v++) { - final Node propertyNode = propertyNodes.item(v); - if (propertyNode.getNodeType() != Node.ELEMENT_NODE) continue; - - final String propertyName = propertyNode.getNodeName(); - if ("Password".equals(propertyName)) { - password = XmlHelper.getText(propertyNode); - } else if ("Username".equals(propertyName)) { - username = XmlHelper.getText(propertyNode); - } - } - value = new Credential(username, password); - return value; - } - - public Element toXml(final Document document) { - final Element valueNode = document.createElement("value"); - - final Element passwordNode = document.createElement("Password"); - final Text passwordValue = document.createTextNode(this.Password); - passwordNode.appendChild(passwordValue); - valueNode.appendChild(passwordNode); - - final Element usernameNode = document.createElement("Username"); - final Text usernameValue = document.createTextNode(this.Username); - usernameNode.appendChild(usernameValue); - valueNode.appendChild(usernameNode); - - return valueNode; - } - - /** - * Compares an object to this {@link Credential} for equality. - * - * @param obj The object to compare. - * @return True if equal; false otherwise. - */ - @Override - public boolean equals(final Object obj) { - return operatorEquals(this, obj instanceof Credential ? ((Credential) obj) : null); - } - // PORT NOTE: Java doesn't support a specific overload (as per IEquatable) - - /** - * Gets a hash code based on the contents of the {@link Credential}. - * - * @return 32-bit hash code. - */ - @Override - public int hashCode() { - // PORT NOTE: Java doesn't have unchecked blocks; the default behaviour is apparently equivalent. - { - return Username.hashCode() + 7 * Password.hashCode(); - } - } - - public void contributeHeader(final Map headers) { - // credentials are packed into the 'Authorization' header as a base64 encoded pair - final String credPair = Username + ":" + Password; - final byte[] credBytes = credPair.getBytes(ASCII); - final String base64enc = DatatypeConverter.printBase64Binary(credBytes); - headers.put("Authorization", "Basic" + " " + base64enc); - } - - public static void validate(final Credential credentials) { - if (credentials == null) - throw new IllegalArgumentException("The credentials parameter cannot be null"); - if (credentials.Password.length() > PASSWORD_MAX_LENGTH) - throw new IllegalArgumentException(String.format("The Password field of the credentials parameter cannot " + - "be longer than %1$d characters.", PASSWORD_MAX_LENGTH)); - if (credentials.Username.length() > USERNAME_MAX_LENGTH) - throw new IllegalArgumentException(String.format("The Username field of the credentials parameter cannot " + - "be longer than %1$d characters.", USERNAME_MAX_LENGTH)); - } - - /** - * Compares two credentials for equality. - * - * @param credential1 Credential to compare. - * @param credential2 Credential to compare. - * @return True if equal; false otherwise. - */ - public static boolean operatorEquals(final Credential credential1, final Credential credential2) { - if (credential1 == credential2) - return true; - if ((credential1 == null) || (null == credential2)) - return false; - - return credential1.Username.equals(credential2.Username) - && credential1.Password.equals(credential2.Password); - } - - /** - * Compares two credentials for inequality. - * - * @param credential1 Credential to compare. - * @param credential2 Credential to compare. - * @return False if equal; true otherwise. - */ - public static boolean operatorNotEquals(final Credential credential1, final Credential credential2) { - return !operatorEquals(credential1, credential2); - } -} diff --git a/common/src/main/java/com/microsoft/alm/secret/Secret.java b/common/src/main/java/com/microsoft/alm/secret/Secret.java deleted file mode 100644 index c021169c..00000000 --- a/common/src/main/java/com/microsoft/alm/secret/Secret.java +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.secret; - -import com.microsoft.alm.helpers.Debug; -import com.microsoft.alm.helpers.StringHelper; -import com.microsoft.alm.helpers.UriHelper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.URI; - -public abstract class Secret { - - private static final Logger logger = LoggerFactory.getLogger(Secret.class); - - public static String uriToName(final URI targetUri, final String namespace) { - final String TokenNameBaseFormat = "%1$s:%2$s://%3$s"; - final String TokenNamePortFormat = TokenNameBaseFormat + ":%4$s"; - - Debug.Assert(targetUri != null, "The targetUri parameter is null"); - - logger.debug("Secret::uriToName"); - - String targetName = null; - // trim any trailing slashes and/or whitespace for compat with git-credential-winstore - String trimmedHostUrl = StringHelper.trimEnd(StringHelper.trimEnd(UriHelper.getFullAccount(targetUri), '/', '\\')); - - if (targetUri.getPort() == -1 /* isDefaultPort */) { - targetName = String.format(TokenNameBaseFormat, namespace, targetUri.getScheme(), trimmedHostUrl); - } else { - targetName = String.format(TokenNamePortFormat, namespace, targetUri.getScheme(), trimmedHostUrl, targetUri.getPort()); - } - - logger.debug(" target name = {}", targetName); - - return targetName; - } - - public interface IUriNameConversion { - String convert(final URI targetUri, final String namespace); - } - - public static IUriNameConversion DefaultUriNameConversion = new IUriNameConversion() { - - @Override - public String convert(final URI targetUri, final String namespace) { - return Secret.uriToName(targetUri, namespace); - } - }; - - public static class PrefixedUriNameConversion implements IUriNameConversion { - - private final String prefix; - - public PrefixedUriNameConversion(final String prefix) { - this.prefix = prefix; - } - - @Override - public String convert(final URI targetUri, final String namespace) { - return Secret.uriToName(targetUri, prefix + namespace); - } - } -} diff --git a/common/src/main/java/com/microsoft/alm/secret/Token.java b/common/src/main/java/com/microsoft/alm/secret/Token.java deleted file mode 100644 index 8c4cddbf..00000000 --- a/common/src/main/java/com/microsoft/alm/secret/Token.java +++ /dev/null @@ -1,345 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.secret; - -import com.microsoft.alm.helpers.Debug; -import com.microsoft.alm.helpers.Guid; -import com.microsoft.alm.helpers.NotImplementedException; -import com.microsoft.alm.helpers.StringHelper; -import com.microsoft.alm.helpers.XmlHelper; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.w3c.dom.Text; - -import javax.xml.bind.DatatypeConverter; -import java.nio.ByteBuffer; -import java.util.EnumSet; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicReference; - -/** - * A security token, usually acquired by some authentication and identity services. - */ -public class Token extends Secret { - - private static final Logger logger = LoggerFactory.getLogger(Token.class); - - private static final int sizeofTokenType = 4; - private static final int sizeofGuid = 16; - - public static boolean getFriendlyNameFromType(final TokenType type, final AtomicReference name) { - // PORT NOTE: Java doesn't have the concept of out-of-range enums - - name.set(null); - - name.set(type.getDescription() == null - ? type.toString() - : type.getDescription()); - - return name.get() != null; - } - - public static boolean getTypeFromFriendlyName(final String name, final AtomicReference type) { - Debug.Assert(!StringHelper.isNullOrWhiteSpace(name), "The name parameter is null or invalid"); - - type.set(TokenType.Unknown); - - for (final TokenType value : EnumSet.allOf(TokenType.class)) { - type.set(value); - - AtomicReference typename = new AtomicReference(); - if (getFriendlyNameFromType(type.get(), typename)) { - if (name.equalsIgnoreCase(typename.get())) - return true; - } - } - - return false; - } - - public Token(final String value, final TokenType type) { - Debug.Assert(!StringHelper.isNullOrWhiteSpace(value), "The value parameter is null or invalid"); - // PORT NOTE: Java doesn't have the concept of out-of-range enums - - this.Type = type; - this.Value = value; - } - - public Token(final String value, final String typeName) { - Debug.Assert(!StringHelper.isNullOrWhiteSpace(value), "The value parameter is null or invalid"); - Debug.Assert(!StringHelper.isNullOrWhiteSpace(typeName), "The typeName parameter is null or invalid"); - - AtomicReference type = new AtomicReference(); - if (!getTypeFromFriendlyName(typeName, type)) { - throw new IllegalArgumentException("Unexpected token type '" + typeName + "' encountered"); - } - this.Type = type.get(); - this.Value = value; - } - - // PORT NOTE: ADAL-specific constructor omitted - - /** - * The type of the security token. - */ - public final TokenType Type; - /** - * The raw contents of the token. - */ - public final String Value; - - UUID targetIdentity = Guid.Empty; - - public static Token fromXml(final Node tokenNode) { - Token value; - - String tokenValue = null; - TokenType tokenType = null; - UUID targetIdentity = Guid.Empty; - - final NodeList propertyNodes = tokenNode.getChildNodes(); - for (int v = 0; v < propertyNodes.getLength(); v++) { - final Node propertyNode = propertyNodes.item(v); - final String propertyName = propertyNode.getNodeName(); - if ("Type".equals(propertyName)) { - tokenType = TokenType.valueOf(TokenType.class, XmlHelper.getText(propertyNode)); - } else if ("Value".equals(propertyName)) { - tokenValue = XmlHelper.getText(propertyNode); - } else if ("targetIdentity".equals(propertyName)) { - targetIdentity = UUID.fromString(XmlHelper.getText(propertyNode)); - } - } - value = new Token(tokenValue, tokenType); - value.setTargetIdentity(targetIdentity); - return value; - } - - public Element toXml(final Document document) { - final Element valueNode = document.createElement("value"); - - final Element typeNode = document.createElement("Type"); - final Text typeValue = document.createTextNode(this.Type.toString()); - typeNode.appendChild(typeValue); - valueNode.appendChild(typeNode); - - final Element tokenValueNode = document.createElement("Value"); - final Text valueValue = document.createTextNode(this.Value); - tokenValueNode.appendChild(valueValue); - valueNode.appendChild(tokenValueNode); - - if (!Guid.Empty.equals(this.getTargetIdentity())) { - final Element targetIdentityNode = document.createElement("targetIdentity"); - final Text targetIdentityValue = document.createTextNode(this.getTargetIdentity().toString()); - targetIdentityNode.appendChild(targetIdentityValue); - valueNode.appendChild(targetIdentityNode); - } - return valueNode; - } - - /** - * @return The guid form Identity of the target - */ - public UUID getTargetIdentity() { - return targetIdentity; - } - - public void setTargetIdentity(final UUID targetIdentity) { - this.targetIdentity = targetIdentity; - } - - /** - * Compares an object to this {@link Token} for equality. - * - * @param obj The object to compare. - * @return True is equal; false otherwise. - */ - @Override - public boolean equals(final Object obj) { - return operatorEquals(this, obj instanceof Token ? ((Token) obj) : null); - } - // PORT NOTE: Java doesn't support a specific overload (as per IEquatable) - - /** - * Gets a hash code based on the contents of the token. - * - * @return 32-bit hash code. - */ - @Override - public int hashCode() { - // PORT NOTE: Java doesn't have unchecked blocks; the default behaviour is apparently equivalent. - { - return Type.getValue() * Value.hashCode(); - } - } - - /** - * Converts the token to a human friendly string. - * - * @return Humanish name of the token. - */ - @Override - public String toString() { - final AtomicReference value = new AtomicReference(); - if (getFriendlyNameFromType(Type, value)) - return value.get(); - else - return super.toString(); - } - - public void contributeHeader(final Map headers) { - // different types of tokens are packed differently - switch (Type) { - case Access: - final String prefix = "Bearer"; - headers.put("Authorization", prefix + " " + Value); - break; - case Personal: - final byte[] authData = StringHelper.UTF8GetBytes("PersonalAccessToken:" + Value); - final String base64EncodedAuthData = DatatypeConverter.printBase64Binary(authData); - headers.put("Authorization", "Basic " + base64EncodedAuthData); - break; - case Federated: - throw new NotImplementedException(449222); - default: - final String template = "Tokens of type '%1$s' cannot be used for headers."; - final String message = String.format(template, Type); - throw new IllegalStateException(message); - } - } - - static boolean deserialize(final byte[] bytes, final TokenType type, final AtomicReference tokenReference) { - Debug.Assert(bytes != null, "The bytes parameter is null"); - Debug.Assert(bytes.length > 0, "The bytes parameter is too short"); - Debug.Assert(type != null, "The type parameter is invalid"); - - tokenReference.set(null); - - try { - final int preamble = sizeofTokenType + sizeofGuid; - - if (bytes.length > preamble) { - TokenType readType; - UUID targetIdentity; - - final ByteBuffer p = ByteBuffer.wrap(bytes); // PORT NOTE: ByteBuffer is closest to "fixed" - { - readType = TokenType.fromValue(Integer.reverseBytes(p.getInt())); - byte[] guidBytes = new byte[16]; - p.get(guidBytes); - targetIdentity = Guid.fromBytes(guidBytes); - } - - if (readType == type) { - final String value = StringHelper.UTF8GetString(bytes, preamble, bytes.length - preamble); - - if (!StringHelper.isNullOrWhiteSpace(value)) { - tokenReference.set(new Token(value, type)); - tokenReference.get().targetIdentity = targetIdentity; - } - } - } - - // if value hasn't been set yet, fall back to old format decode - if (tokenReference.get() == null) { - final String value = StringHelper.UTF8GetString(bytes); - - if (!StringHelper.isNullOrWhiteSpace(value)) { - tokenReference.set(new Token(value, type)); - } - } - } catch (final Throwable throwable) { - logger.debug(" token deserialization error"); - } - - return tokenReference.get() != null; - } - - static boolean serialize(final Token token, final AtomicReference byteReference) { - Debug.Assert(token != null, "The token parameter is null"); - Debug.Assert(!StringHelper.isNullOrWhiteSpace(token.Value), "The token.Value is invalid"); - - byteReference.set(null); - - try { - final byte[] utf8bytes = StringHelper.UTF8GetBytes(token.Value); - final ByteBuffer bytes = ByteBuffer.allocate(utf8bytes.length + sizeofTokenType + sizeofGuid); - - // PORT NOTE: "fixed" block pointer arithmetic and casting avoided - { - bytes.putInt(Integer.reverseBytes(token.Type.getValue())); - bytes.put(Guid.toBytes(token.targetIdentity)); - } - - bytes.put(utf8bytes); - byteReference.set(bytes.array()); - } catch (final Throwable t) { - logger.debug(" token serialization error"); - } - - return byteReference.get() != null; - } - - public static void validate(final Token token) { - if (token == null) - throw new IllegalArgumentException("The `token` parameter is null or invalid."); - if (StringHelper.isNullOrWhiteSpace(token.Value)) - throw new IllegalArgumentException("The value of the `token` cannot be null or empty."); - if (token.Value.length() > Credential.PASSWORD_MAX_LENGTH) - throw new IllegalArgumentException(String.format("The value of the `token` cannot be longer than %1$d " + - "characters.", Credential.PASSWORD_MAX_LENGTH)); - } - - /** - * Explicitly casts a personal access token token into a set of credentials - * - * @param token The {@link Token} to convert. - * @return A corresponding {@link Credential} instance. - * @throws IllegalArgumentException if the {@link Token#Type} is not {@link TokenType#Personal}. - */ - // PORT NOTE: Java doesn't have cast operator overloading - public static Credential toCredential(final Token token) { - if (token == null) - return null; - - if (token.Type != TokenType.Personal) - throw new IllegalArgumentException("Cannot convert " + token.toString() + " to credentials"); - - return new Credential(token.toString(), token.Value); - } - - /** - * Compares two tokens for equality. - * - * @param token1 Token to compare. - * @param token2 Token to compare. - * @return True if equal; false otherwise. - */ - public static boolean operatorEquals(final Token token1, final Token token2) { - if (token1 == token2) - return true; - if ((token1 == null) || (null == token2)) - return false; - - return token1.Type == token2.Type - && token1.Value.equalsIgnoreCase(token2.Value); - } - - /** - * Compares two tokens for inequality. - * - * @param token1 Token to compare. - * @param token2 Token to compare. - * @return False if equal; true otherwise. - */ - public static boolean operatorNotEquals(final Token token1, final Token token2) { - return !operatorEquals(token1, token2); - } - - -} diff --git a/common/src/main/java/com/microsoft/alm/secret/TokenPair.java b/common/src/main/java/com/microsoft/alm/secret/TokenPair.java deleted file mode 100644 index 380c4d14..00000000 --- a/common/src/main/java/com/microsoft/alm/secret/TokenPair.java +++ /dev/null @@ -1,210 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.secret; - -import com.microsoft.alm.helpers.Debug; -import com.microsoft.alm.helpers.PropertyBag; -import com.microsoft.alm.helpers.StringHelper; -import com.microsoft.alm.helpers.XmlHelper; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.w3c.dom.Text; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; - -public class TokenPair extends Secret { - private static final Map EMPTY_MAP = Collections.unmodifiableMap(new LinkedHashMap(0)); - private static final String ACCESS_TOKEN = "access_token"; - private static final String REFRESH_TOKEN = "refresh_token"; - - /** - * Creates a new {@link TokenPair} from raw access and refresh token data. - * - * @param accessToken The base64 encoded value of the access token's raw data - * @param refreshToken The base64 encoded value of the refresh token's raw data - */ - public TokenPair(final String accessToken, final String refreshToken) { - Debug.Assert(!StringHelper.isNullOrWhiteSpace(accessToken), "The accessToken parameter is null or invalid."); - Debug.Assert(!StringHelper.isNullOrWhiteSpace(refreshToken), "The refreshToken parameter is null or invalid."); - - this.AccessToken = new Token(accessToken, TokenType.Access); - this.RefreshToken = new Token(refreshToken, TokenType.Refresh); - this.Parameters = EMPTY_MAP; - } - - public TokenPair(final String accessTokenResponse) { - this(PropertyBag.fromJson(accessTokenResponse)); - } - - public TokenPair(final PropertyBag bag) { - final LinkedHashMap parameters = new LinkedHashMap(); - String accessToken = null; - String refreshToken = null; - for (final Map.Entry pair : bag.entrySet()) { - final String name = pair.getKey(); - final Object value = pair.getValue(); - if (ACCESS_TOKEN.equals(name)) { - accessToken = (String) value; - } - else if (REFRESH_TOKEN.equals(name)) { - refreshToken = (String) value; - } - else { - parameters.put(name, value.toString()); - } - - } - this.AccessToken = new Token(accessToken, TokenType.Access); - this.RefreshToken = new Token(refreshToken, TokenType.Refresh); - this.Parameters = Collections.unmodifiableMap(parameters); - } - - /** - * Access token, used to grant access to resources. - */ - public final Token AccessToken; - /** - * Refresh token, used to grant new access tokens. - */ - public final Token RefreshToken; - public final Map Parameters; - - public static TokenPair fromXml(final Node tokenPairNode) { - TokenPair value; - - String accessToken = null; - String refreshToken = null; - - final NodeList propertyNodes = tokenPairNode.getChildNodes(); - for (int v = 0; v < propertyNodes.getLength(); v++) { - final Node propertyNode = propertyNodes.item(v); - final String propertyName = propertyNode.getNodeName(); - if ("accessToken".equals(propertyName)) { - accessToken = XmlHelper.getText(propertyNode); - } else if ("refreshToken".equals(propertyName)) { - refreshToken = XmlHelper.getText(propertyNode); - } - } - - value = new TokenPair(accessToken, refreshToken); - return value; - } - - public Element toXml(final Document document) { - final Element valueNode = document.createElement("value"); - - final Element accessTokenNode = document.createElement("accessToken"); - final Text accessTokenValue = document.createTextNode(AccessToken.Value); - accessTokenNode.appendChild(accessTokenValue); - valueNode.appendChild(accessTokenNode); - - final Element refreshTokenNode = document.createElement("refreshToken"); - final Text refreshTokenValue = document.createTextNode(RefreshToken.Value); - refreshTokenNode.appendChild(refreshTokenValue); - valueNode.appendChild(refreshTokenNode); - - return valueNode; - } - - public static String toXmlString(final TokenPair tokenPair) { - final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - try { - final DocumentBuilder builder = dbf.newDocumentBuilder(); - final Document document = builder.newDocument(); - - final Element element = tokenPair.toXml(document); - document.appendChild(element); - - final String result = XmlHelper.toString(document); - - return result; - } - catch (final Exception e) { - throw new Error(e); - } - } - - public static TokenPair fromXmlString(final String xmlString) { - final byte[] bytes = StringHelper.UTF8GetBytes(xmlString); - final ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); - return fromXmlStream(inputStream); - } - - static TokenPair fromXmlStream(final InputStream source) { - final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); - try { - final DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder(); - final Document document = builder.parse(source); - final Element rootElement = document.getDocumentElement(); - - final TokenPair result = TokenPair.fromXml(rootElement); - - return result; - } - catch (final Exception e) { - throw new Error(e); - } - } - - /** - * Compares an object to this. - * - * @param object The object to compare. - * @return True if equal; false otherwise - */ - @Override - public boolean equals(final Object object) { - return operatorEquals(this, object instanceof TokenPair ? ((TokenPair) object) : null); - } - // PORT NOTE: Java doesn't support a specific overload (as per IEquatable) - - /** - * Gets a hash code based on the contents of the {@link TokenPair}. - * - * @return 32-bit hash code. - */ - @Override - public int hashCode() { - // PORT NOTE: Java doesn't have unchecked blocks; the default behaviour is apparently equivalent. - { - return AccessToken.hashCode() * RefreshToken.hashCode(); - } - } - - /** - * Compares two {@link TokenPair} for equality. - * - * @param pair1 {@link TokenPair} to compare. - * @param pair2 {@link TokenPair} to compare. - * @return True if equal; false otherwise. - */ - public static boolean operatorEquals(final TokenPair pair1, final TokenPair pair2) { - if (pair1 == pair2) - return true; - if ((pair1 == null) || (null == pair2)) - return false; - - return Token.operatorEquals(pair1.AccessToken, pair2.AccessToken) - && Token.operatorEquals(pair1.RefreshToken, pair2.RefreshToken); - } - - /** - * Compares two {@link TokenPair} for inequality. - * - * @param pair1 {@link TokenPair} to compare. - * @param pair2 {@link TokenPair} to compare. - * @return False if equal; true otherwise. - */ - public static boolean operatorNotEquals(final TokenPair pair1, final TokenPair pair2) { - return !operatorEquals(pair1, pair2); - } -} diff --git a/common/src/main/java/com/microsoft/alm/secret/TokenScope.java b/common/src/main/java/com/microsoft/alm/secret/TokenScope.java deleted file mode 100644 index 899bbd93..00000000 --- a/common/src/main/java/com/microsoft/alm/secret/TokenScope.java +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.secret; - -import com.microsoft.alm.helpers.ScopeSet; -import com.microsoft.alm.helpers.StringHelper; - -public abstract class TokenScope { - private static final String[] EmptyStringArray = new String[0]; - - protected TokenScope(final String value) { - if (StringHelper.isNullOrWhiteSpace(value)) { - _scopes = new String[0]; - } else { - _scopes = new String[1]; - _scopes[0] = value; - } - } - - protected TokenScope(final String[] values) { - _scopes = values; - } - - protected TokenScope(final ScopeSet set) { - //noinspection ToArrayCallWithZeroLengthArrayArgument - _scopes = set.toArray(EmptyStringArray); - } - - public String getValue() { - return StringHelper.join(" ", _scopes); - } - - protected final String[] _scopes; - - @Override - public boolean equals(final Object obj) { - return operatorEquals(this, obj instanceof TokenScope ? ((TokenScope) obj) : null); - } - - @Override - public int hashCode() { - // largest 31-bit prime (https://msdn.microsoft.com/en-us/library/Ee621251.aspx) - int hash = 2147483647; - - for (int i = 0; i < _scopes.length; i++) { - // PORT NOTE: Java doesn't have unchecked blocks; the default behaviour is apparently equivalent. - { - hash ^= _scopes[i].hashCode(); - } - } - - return hash; - } - - @Override - public String toString() { - return getValue(); - } - - public static boolean operatorEquals(final TokenScope scope1, final TokenScope scope2) { - if (scope1 == scope2) - return true; - if ((scope1 == null) || (null == scope2)) - return false; - - final ScopeSet set = new ScopeSet(); - set.unionWith(scope1._scopes); - return set.setEquals(scope2._scopes); - } - - public static boolean operatorNotEquals(final TokenScope scope1, final TokenScope scope2) { - return !operatorEquals(scope1, scope2); - } -} diff --git a/common/src/main/java/com/microsoft/alm/secret/TokenType.java b/common/src/main/java/com/microsoft/alm/secret/TokenType.java deleted file mode 100644 index 392dd7b8..00000000 --- a/common/src/main/java/com/microsoft/alm/secret/TokenType.java +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.secret; - -import java.util.HashMap; -import java.util.Map; - -public enum TokenType { - Unknown(null, 0), - /** - * Azure Directory Access Token - */ - Access("Azure Directory Access Token", 1), - /** - * Azure Directory Refresh Token - */ - Refresh("Azure Directory Refresh Token", 2), - /** - * Personal Access Token, can be compact or not. - */ - Personal("Personal Access Token", 3), - /** - * Federated Authentication (aka FedAuth) Token - */ - Federated("Federated Authentication Token", 4), - /** - * Used only for testing - */ - Test("Test-only Token", 5); - - private static final Map valueToTokenType; - - static { - valueToTokenType = new HashMap(); - for (final TokenType value : TokenType.values()) { - valueToTokenType.put(value.getValue(), value); - } - } - - private final String description; - private final int value; - - private TokenType(final String description, final int value) { - this.description = description; - this.value = value; - } - - public String getDescription() { - return description; - } - - public int getValue() { - return value; - } - - public static TokenType fromValue(final int value) { - return valueToTokenType.get(value); - } -} diff --git a/common/src/main/java/com/microsoft/alm/secret/VsoTokenScope.java b/common/src/main/java/com/microsoft/alm/secret/VsoTokenScope.java deleted file mode 100644 index 86a1e966..00000000 --- a/common/src/main/java/com/microsoft/alm/secret/VsoTokenScope.java +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.secret; - -import com.microsoft.alm.helpers.ScopeSet; -import com.microsoft.alm.helpers.StringHelper; - -import java.util.Arrays; -import java.util.Iterator; -import java.util.List; - -public class VsoTokenScope extends TokenScope { - /** - * Grants permissions to all resources. This scope is required for making SOAP calls. - */ - public static final VsoTokenScope AllScopes = new VsoTokenScope(StringHelper.Empty); - - /** - * Grants the ability to access build artifacts, including build results, definitions, and - * requests, and the ability to receive notifications about build events via service hooks. - */ - public static final VsoTokenScope BuildAccess = new VsoTokenScope("vso.build"); - /** - * Grants the ability to access build artifacts, including build results, definitions, and - * requests, and the ability to queue a build, update build properties, and the ability to - * receive notifications about build events via service hooks. - */ - public static final VsoTokenScope BuildExecute = new VsoTokenScope("vso.build_execute"); - /** - * Grants the ability to access rooms and view, post, and update messages. Also grants the - * ability to manage rooms and users and to receive notifications about new messages via - * service hooks. - */ - public static final VsoTokenScope ChatManage = new VsoTokenScope("vso.chat_manage"); - /** - * Grants the ability to access rooms and view, post, and update messages. Also grants the - * ability to receive notifications about new messages via service hooks. - */ - public static final VsoTokenScope ChatWrite = new VsoTokenScope("vso.chat_write"); - /** - * Grants the ability to read, update, and delete source code, access metadata about - * commits, changesets, branches, and other version control artifacts. Also grants the - * ability to create and manage code repositories, create and manage pull requests and - * code reviews, and to receive notifications about version control events via service - * hooks. - */ - public static final VsoTokenScope CodeManage = new VsoTokenScope("vso.code_manage"); - /** - * Grants the ability to read source code and metadata about commits, changesets, branches, - * and other version control artifacts. Also grants the ability to get notified about - * version control events via service hooks. - */ - public static final VsoTokenScope CodeRead = new VsoTokenScope("vso.code"); - /** - * Grants the ability to read, update, and delete source code, access metadata about - * commits, changesets, branches, and other version control artifacts. Also grants the - * ability to create and manage pull requests and code reviews and to receive - * notifications about version control events via service hooks. - */ - public static final VsoTokenScope CodeWrite = new VsoTokenScope("vso.code_write"); - /** - * Grants the ability to read, write, and delete feeds and packages. - */ - public static final VsoTokenScope PackagingManage = new VsoTokenScope("vso.packaging_manage"); - /** - * Grants the ability to list feeds and read packages in those feeds. - */ - public static final VsoTokenScope PackagingRead = new VsoTokenScope("vso.packaging"); - /** - * Grants the ability to list feeds and read, write, and delete packages in those feeds. - */ - public static final VsoTokenScope PackagingWrite = new VsoTokenScope("vso.packaging_write"); - /** - * Grants the ability to read your profile, accounts, collections, projects, teams, and - * other top-level organizational artifacts. - */ - public static final VsoTokenScope ProfileRead = new VsoTokenScope("vso.profile"); - /** - * Grants the ability to read service hook subscriptions and metadata, including supported - * events, consumers, and actions. - */ - public static final VsoTokenScope ServiceHookRead = new VsoTokenScope("vso.hooks"); - /** - * Grants the ability to create and update service hook subscriptions and read metadata, - * including supported events, consumers, and actions." - */ - public static final VsoTokenScope ServiceHookWrite = new VsoTokenScope("vso.hooks_write"); - /** - * Grants the ability to read test plans, cases, results and other test management related - * artifacts. - */ - public static final VsoTokenScope TestRead = new VsoTokenScope("vso.test"); - /** - * Grants the ability to read, create, and update test plans, cases, results and other - * test management related artifacts. - */ - public static final VsoTokenScope TestWrite = new VsoTokenScope("vso.test_write"); - /** - * Grants the ability to read work items, queries, boards, area and iterations paths, and - * other work item tracking related metadata. Also grants the ability to execute queries - * and to receive notifications about work item events via service hooks. - */ - public static final VsoTokenScope WorkRead = new VsoTokenScope("vso.work"); - /** - * Grants the ability to read, create, and update work items and queries, update board - * metadata, read area and iterations paths other work item tracking related metadata, - * execute queries, and to receive notifications about work item events via service hooks. - */ - public static final VsoTokenScope WorkWrite = new VsoTokenScope("vso.work_write"); - - - private VsoTokenScope(final String value) { - super(value); - } - - private VsoTokenScope(final String[] values) { - super(values); - } - - private VsoTokenScope(final ScopeSet set) { - super(set); - } - - private static final VsoTokenScope[] scopeArray = { - BuildAccess, - BuildExecute, - ChatManage, - ChatWrite, - CodeManage, - CodeRead, - CodeWrite, - PackagingManage, - PackagingRead, - PackagingWrite, - ProfileRead, - ServiceHookRead, - ServiceHookWrite, - TestRead, - TestWrite, - WorkRead, - WorkWrite - }; - - private static final List values = Arrays.asList(scopeArray); - - public static final VsoTokenScope CodeAll = or(CodeManage, CodeRead, CodeWrite); - - public static Iterator enumerateValues() { - return values.iterator(); - } - - public static VsoTokenScope or(final VsoTokenScope... scopes) { - final ScopeSet set = new ScopeSet(); - - for (final VsoTokenScope scope : scopes) { - set.unionWith(scope._scopes); - } - - return new VsoTokenScope(set); - } - - public static VsoTokenScope and(final VsoTokenScope... scopes) { - final ScopeSet set = new ScopeSet(); - set.unionWith(scopes[0]._scopes); - - for (final VsoTokenScope scope : scopes) { - set.intersectWith(scope._scopes); - } - - return new VsoTokenScope(set); - } - -} diff --git a/common/src/main/java/com/microsoft/alm/storage/InsecureFileBackedCredentialStore.java b/common/src/main/java/com/microsoft/alm/storage/InsecureFileBackedCredentialStore.java deleted file mode 100644 index 991a45d8..00000000 --- a/common/src/main/java/com/microsoft/alm/storage/InsecureFileBackedCredentialStore.java +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.storage; - -import com.microsoft.alm.secret.Credential; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static com.microsoft.alm.helpers.LoggingHelper.logError; - -public class InsecureFileBackedCredentialStore implements SecretStore { - - private static Logger logger = LoggerFactory.getLogger(InsecureFileBackedCredentialStore.class); - - private static InsecureFileBackend fileBackend = InsecureFileBackend.getInstance(); - - @Override - public Credential get(String key) { - return fileBackend.readCredentials(key); - } - - @Override - public boolean delete(String key) { - return fileBackend.delete(key); - } - - @Override - public boolean add(String key, Credential secret) { - try { - fileBackend.writeCredential(key, secret); - - return true; - } catch (final Throwable t) { - logError(logger, "Failed to add secret to file backed credential store.", t); - - return false; - } - } - - @Override - public boolean isSecure() { - return false; - } -} diff --git a/common/src/main/java/com/microsoft/alm/storage/InsecureFileBackedTokenStore.java b/common/src/main/java/com/microsoft/alm/storage/InsecureFileBackedTokenStore.java deleted file mode 100644 index 5f972686..00000000 --- a/common/src/main/java/com/microsoft/alm/storage/InsecureFileBackedTokenStore.java +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.storage; - -import com.microsoft.alm.secret.Token; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import static com.microsoft.alm.helpers.LoggingHelper.logError; - -public class InsecureFileBackedTokenStore implements SecretStore { - - private static Logger logger = LoggerFactory.getLogger(InsecureFileBackedTokenStore.class); - - private static InsecureFileBackend fileBackend = InsecureFileBackend.getInstance(); - - @Override - public Token get(String key) { - return fileBackend.readToken(key); - } - - @Override - public boolean delete(String key) { - return fileBackend.delete(key); - } - - @Override - public boolean add(String key, Token secret) { - try { - fileBackend.writeToken(key, secret); - - return true; - } catch (final Throwable t) { - logError(logger, "Failed to add secret to file backed token store.", t); - return false; - } - } - - @Override - public boolean isSecure() { - return false; - } -} diff --git a/common/src/main/java/com/microsoft/alm/storage/InsecureFileBackend.java b/common/src/main/java/com/microsoft/alm/storage/InsecureFileBackend.java deleted file mode 100644 index 8e1327f7..00000000 --- a/common/src/main/java/com/microsoft/alm/storage/InsecureFileBackend.java +++ /dev/null @@ -1,321 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.storage; - -import com.microsoft.alm.helpers.Environment; -import com.microsoft.alm.helpers.IOHelper; -import com.microsoft.alm.helpers.SystemHelper; -import com.microsoft.alm.helpers.XmlHelper; -import com.microsoft.alm.secret.Credential; -import com.microsoft.alm.secret.Token; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.w3c.dom.Node; -import org.w3c.dom.NodeList; -import org.w3c.dom.Text; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.transform.OutputKeys; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.HashMap; -import java.util.Map; - -import static com.microsoft.alm.helpers.LoggingHelper.logError; - -class InsecureFileBackend { - - private static final Logger logger = LoggerFactory.getLogger(InsecureFileBackend.class); - - public static final String PROGRAM_FOLDER_NAME = "VSTeamServicesAuthPlugin"; - - private final File backingFile; - - final Map Tokens = new HashMap(); - final Map Credentials = new HashMap(); - - private static InsecureFileBackend instance; - - public static synchronized InsecureFileBackend getInstance() { - if (instance == null) { - instance = new InsecureFileBackend(getBackingFile()); - } - - return instance; - } - - /** - * Creates an instance that reads from and writes to the specified backingFile. - * - * @param backingFile the file to read from and write to. Does not need to exist first. - */ - InsecureFileBackend(final File backingFile) { - this.backingFile = backingFile; - reload(); - } - - void reload() { - if (backingFile != null && backingFile.isFile() && backingFile.length() > 0) { - FileInputStream fis = null; - try { - fis = new FileInputStream(backingFile); - final InsecureFileBackend clone = fromXml(fis); - if (clone != null) { - this.Tokens.clear(); - this.Tokens.putAll(clone.Tokens); - - this.Credentials.clear(); - this.Credentials.putAll(clone.Credentials); - } - } catch (final FileNotFoundException e) { - logger.info("backingFile {} did not exist", backingFile.getAbsolutePath()); - } finally { - IOHelper.closeQuietly(fis); - } - } - } - - void save() { - if (backingFile != null) { - // TODO: 449510: consider creating a backup of the file, if it exists, before overwriting it - FileOutputStream fos = null; - try { - fos = new FileOutputStream(backingFile); - toXml(fos); - } catch (final FileNotFoundException e) { - throw new Error("Error during save()", e); - } finally { - IOHelper.closeQuietly(fos); - } - - if (!backingFile.setReadable(false, false) - || !backingFile.setWritable(false, false) - || !backingFile.setExecutable(false, false)) { - logger.warn("Unable to remove file permissions for everybody: {}", backingFile); - } - if (!backingFile.setReadable(true, true) - || !backingFile.setWritable(true, true) - || !backingFile.setExecutable(false, true)) { - logger.warn("Unable to set file permissions for owner: {}", backingFile); - } - } - } - - static InsecureFileBackend fromXml(final InputStream source) { - try { - final InsecureFileBackend result = new InsecureFileBackend(null); - final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - final DocumentBuilder builder = dbf.newDocumentBuilder(); - final Document document = builder.parse(source); - final Element insecureStoreElement = document.getDocumentElement(); - - final NodeList tokensOrCredentialsList = insecureStoreElement.getChildNodes(); - for (int toc = 0; toc < tokensOrCredentialsList.getLength(); toc++) { - final Node tokensOrCredentials = tokensOrCredentialsList.item(toc); - if (tokensOrCredentials.getNodeType() != Node.ELEMENT_NODE) - continue; - if ("Tokens".equals(tokensOrCredentials.getNodeName())) { - result.Tokens.clear(); - } else if ("Credentials".equals(tokensOrCredentials.getNodeName())) { - result.Credentials.clear(); - } else continue; - final NodeList entryList = tokensOrCredentials.getChildNodes(); - for (int e = 0; e < entryList.getLength(); e++) { - final Node entryNode = entryList.item(e); - if (entryNode.getNodeType() != Node.ELEMENT_NODE || !"entry".equals(entryNode.getNodeName())) - continue; - if ("Tokens".equals(tokensOrCredentials.getNodeName())) { - loadToken(result, entryNode); - } else if ("Credentials".equals(tokensOrCredentials.getNodeName())) { - loadCredential(result, entryNode); - } - } - } - return result; - } catch (final Exception e) { - logError(logger, "Warning: unable to deserialize InsecureFileBackend. Is the file corrupted?", e); - return null; - } - } - - private static void loadCredential(final InsecureFileBackend result, final Node entryNode) { - String key = null; - Credential value = null; - final NodeList keyOrValueList = entryNode.getChildNodes(); - for (int kov = 0; kov < keyOrValueList.getLength(); kov++) { - final Node keyOrValueNode = keyOrValueList.item(kov); - if (keyOrValueNode.getNodeType() != Node.ELEMENT_NODE) continue; - - final String keyOrValueName = keyOrValueNode.getNodeName(); - if ("key".equals(keyOrValueName)) { - key = XmlHelper.getText(keyOrValueNode); - } else if ("value".equals(keyOrValueName)) { - value = Credential.fromXml(keyOrValueNode); - } - } - result.Credentials.put(key, value); - } - - private static void loadToken(final InsecureFileBackend result, final Node entryNode) { - String key = null; - Token value = null; - final NodeList keyOrValueList = entryNode.getChildNodes(); - for (int kov = 0; kov < keyOrValueList.getLength(); kov++) { - final Node keyOrValueNode = keyOrValueList.item(kov); - if (keyOrValueNode.getNodeType() != Node.ELEMENT_NODE) continue; - final String keyOrValueName = keyOrValueNode.getNodeName(); - if ("key".equals(keyOrValueName)) { - key = XmlHelper.getText(keyOrValueNode); - } else if ("value".equals(keyOrValueName)) { - value = Token.fromXml(keyOrValueNode); - } - } - result.Tokens.put(key, value); - } - - void toXml(final OutputStream destination) { - try { - final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - final DocumentBuilder builder = dbf.newDocumentBuilder(); - final Document document = builder.newDocument(); - - final Element insecureStoreNode = document.createElement("insecureStore"); - insecureStoreNode.appendChild(createTokensNode(document)); - insecureStoreNode.appendChild(createCredentialsNode(document)); - document.appendChild(insecureStoreNode); - - final TransformerFactory tf = TransformerFactory.newInstance(); - final Transformer transformer = tf.newTransformer(); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer.setOutputProperty(OutputKeys.STANDALONE, "yes"); - //http://johnsonsolutions.blogspot.ca/2007/08/xml-transformer-indent-doesnt-work-with.html - transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4"); - transformer.transform(new DOMSource(document), new StreamResult(destination)); - } catch (final Exception e) { - throw new Error(e); - } - } - - private Element createTokensNode(final Document document) { - final Element tokensNode = document.createElement("Tokens"); - for (final Map.Entry entry : Tokens.entrySet()) { - final Element entryNode = document.createElement("entry"); - - final Element keyNode = document.createElement("key"); - final Text keyValue = document.createTextNode(entry.getKey()); - keyNode.appendChild(keyValue); - entryNode.appendChild(keyNode); - - final Token value = entry.getValue(); - if (value != null) { - final Element valueNode = value.toXml(document); - - entryNode.appendChild(valueNode); - } - - tokensNode.appendChild(entryNode); - } - return tokensNode; - } - - private Element createCredentialsNode(final Document document) { - final Element credentialsNode = document.createElement("Credentials"); - for (final Map.Entry entry : Credentials.entrySet()) { - final Element entryNode = document.createElement("entry"); - - final Element keyNode = document.createElement("key"); - final Text keyValue = document.createTextNode(entry.getKey()); - keyNode.appendChild(keyValue); - entryNode.appendChild(keyNode); - - final Credential value = entry.getValue(); - if (value != null) { - final Element valueNode = value.toXml(document); - - entryNode.appendChild(valueNode); - } - credentialsNode.appendChild(entryNode); - } - return credentialsNode; - } - - public synchronized boolean delete(final String targetName) { - if (Tokens.containsKey(targetName)) { - Tokens.remove(targetName); - save(); - } else if (Credentials.containsKey(targetName)) { - Credentials.remove(targetName); - save(); - } - - return true; - } - - public synchronized Credential readCredentials(final String targetName) { - return Credentials.get(targetName); - } - - public synchronized Token readToken(final String targetName) { - return Tokens.get(targetName); - } - - public synchronized void writeCredential(final String targetName, final Credential credentials) { - Credentials.put(targetName, credentials); - save(); - } - - public synchronized void writeToken(final String targetName, final Token token) { - Tokens.put(targetName, token); - save(); - } - - private static File getBackingFile() { - final File parentFolder = determineParentFolder(); - - // .hidden this folder on *nix system - final String programFolderName = SystemHelper.isWindows() ? PROGRAM_FOLDER_NAME : "." + PROGRAM_FOLDER_NAME; - final File programFolder = new File(parentFolder, programFolderName); - - if (!programFolder.exists()) { - programFolder.mkdirs(); - } - - final File insecureFile = new File(programFolder, "insecureStore.xml"); - - return insecureFile; - } - - private static File determineParentFolder() { - return findFirstValidFolder( - Environment.SpecialFolder.LocalApplicationData, - Environment.SpecialFolder.ApplicationData, - Environment.SpecialFolder.UserProfile); - } - - private static File findFirstValidFolder(final Environment.SpecialFolder... candidates) { - for (final Environment.SpecialFolder candidate : candidates) { - final String path = Environment.getFolderPath(candidate); - if (path == null) - continue; - final File result = new File(path); - if (result.isDirectory()) { - return result; - } - } - final String path = System.getenv("HOME"); - final File result = new File(path); - return result; - } -} diff --git a/common/src/main/resources/EULA.doc b/common/src/main/resources/EULA.doc deleted file mode 100755 index ef8abf8a..00000000 Binary files a/common/src/main/resources/EULA.doc and /dev/null differ diff --git a/common/src/main/resources/ThirdPartyNotices.txt b/common/src/main/resources/ThirdPartyNotices.txt deleted file mode 100755 index db8664c4..00000000 --- a/common/src/main/resources/ThirdPartyNotices.txt +++ /dev/null @@ -1,72 +0,0 @@ - -THIRD-PARTY SOFTWARE NOTICES AND INFORMATION -Do Not Translate or Localize - -Visual Studio Team Services Authentication Library for Java - - - -This project is based on or incorporates material from the projects listed below (Third Party Code). The original copyright notice and the license under which Microsoft received such Third Party Code, are set forth below. Such licenses and notices are provided for informational purposes only. Microsoft licenses the Third Party Code to you under the licensing terms for the Microsoft product. Microsoft reserves all other rights not expressly granted under this agreement, whether by implication, estoppel or otherwise. - - -1. org.eclipse.swt.browser (http://archive.eclipse.org/eclipse/downloads/drops4/R-4.4.2-201502041700/) - - -%% org.eclipse.swt.browser NOTICES AND INFORMATION BEGIN HERE -========================================= -You may obtain the source code for org.eclipse.swt.browser from us, if and as required under the relevant open source license, for a period of one year after our last shipment of this product, by sending a money order or check for $5.00 to: Source Code Compliance Team, Microsoft Corporation, 1 Microsoft Way, Redmond, WA 98052. Please write “source code for Visual Studio Team Services Authentication Library for Java” in the memo line of your payment. We may also make a copy of the source code available at http://thirdpartysource.microsoft.com. - -All past Contributors to the Third Party Code licensed under the Eclipse Public License disclaim all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose. In addition, such Contributors are not liable for any damages, including direct, indirect, special, incidental and consequential damages, such as lost profits. Any provisions of the licensing terms of the Microsoft product that differ from the Eclipse Public License are offered by Microsoft alone and not by any other party. - -Provided for Informational Purposes Only - -Eclipse Public License - v 1.0 -THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. -1. DEFINITIONS -"Contribution" means: -a) in the case of the initial Contributor, the initial code and documentation distributed under this Agreement, and -b) in the case of each subsequent Contributor: -i) changes to the Program, and -ii) additions to the Program; -where such changes and/or additions to the Program originate from and are distributed by that particular Contributor. A Contribution 'originates' from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include additions to the Program which: (i) are separate modules of software distributed in conjunction with the Program under their own license agreement, and (ii) are not derivative works of the Program. -"Contributor" means any person or entity that distributes the Program. -"Licensed Patents" mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. -"Program" means the Contributions distributed in accordance with this Agreement. -"Recipient" means anyone who receives the Program under this Agreement, including all Contributors. -2. GRANT OF RIGHTS -a) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare derivative works of, publicly display, publicly perform, distribute and sublicense the Contribution of such Contributor, if any, and such derivative works, in source code and object code form. -b) Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in source code and object code form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. -c) Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. -d) Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. -3. REQUIREMENTS -A Contributor may choose to distribute the Program in object code form under its own license agreement, provided that: -a) it complies with the terms and conditions of this Agreement; and -b) its license agreement: -i) effectively disclaims on behalf of all Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; -ii) effectively excludes on behalf of all Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; -iii) states that any provisions which differ from this Agreement are offered by that Contributor alone and not by any other party; and -iv) states that source code for the Program is available from such Contributor, and informs licensees how to obtain it in a reasonable manner on or through a medium customarily used for software exchange. -When the Program is made available in source code form: -a) it must be made available under this Agreement; and -b) a copy of this Agreement must be included with each copy of the Program. -Contributors may not remove or alter any copyright notices contained within the Program. -Each Contributor must identify itself as the originator of its Contribution, if any, in a manner that reasonably allows subsequent Recipients to identify the originator of the Contribution. -4. COMMERCIAL DISTRIBUTION -Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor ("Commercial Contributor") hereby agrees to defend and indemnify every other Contributor ("Indemnified Contributor") against any losses, damages and costs (collectively "Losses") arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: a) promptly notify the Commercial Contributor in writing of such claim, and b) allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. -For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. -5. NO WARRANTY -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement , including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. -6. DISCLAIMER OF LIABILITY -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -7. GENERAL -If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. -If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. -All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. -Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to distribute the Program (including its Contributions) under the new version. Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. -This Agreement is governed by the laws of the State of New York and the intellectual property laws of the United States of America. No party to this Agreement will bring a legal action under this Agreement more than one year after the cause of action arose. Each party waives its rights to a jury trial in any resulting litigation. -========================================= -END OF org.eclipse.swt.browser NOTICES AND INFORMATION - - - - diff --git a/common/src/test/groovy/com/microsoft/alm/helpers/PropertyBagTest.groovy b/common/src/test/groovy/com/microsoft/alm/helpers/PropertyBagTest.groovy deleted file mode 100644 index 5972cf30..00000000 --- a/common/src/test/groovy/com/microsoft/alm/helpers/PropertyBagTest.groovy +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers - -import groovy.transform.CompileStatic -import org.junit.Test - -/** - * A class to test {@see PropertyBag}. - */ -@CompileStatic -public class PropertyBagTest { - - @Test public void readOptionalInteger_number() { - final cut = new PropertyBag(); - cut.put("answer", 42.0d); - - final actual = cut.readOptionalInteger("answer", 0); - - assert 42 == actual - } - - @Test public void readOptionalInteger_string() { - final cut = new PropertyBag(); - cut.put("answer", "42"); - - final actual = cut.readOptionalInteger("answer", 0); - - assert 42 == actual - } - - @Test public void readOptionalInteger_otherObject() { - final cut = new PropertyBag(); - cut.put("answer", null); - - final actual = cut.readOptionalInteger("answer", 0); - - assert 0 == actual - } - - @Test public void readOptionalInteger_missing() { - final cut = new PropertyBag(); - - final actual = cut.readOptionalInteger("answer", 0); - - assert 0 == actual - } - -} diff --git a/common/src/test/groovy/com/microsoft/alm/helpers/SimpleJsonTest.groovy b/common/src/test/groovy/com/microsoft/alm/helpers/SimpleJsonTest.groovy deleted file mode 100644 index 329ae19b..00000000 --- a/common/src/test/groovy/com/microsoft/alm/helpers/SimpleJsonTest.groovy +++ /dev/null @@ -1,173 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers - -import groovy.transform.CompileStatic -import org.junit.Assert -import org.junit.Test - -/** - * A class to test {@see SimpleJson}. - */ -@CompileStatic -public class SimpleJsonTest { - - private static void assertParse(final Map expected, final String input) { - final actual = SimpleJson.parse(input) - - assert expected == actual - } - - private static void assertParseError(final String expectedMessage, final String input) { - try { - SimpleJson.parse(input) - } - catch (final IllegalArgumentException actual) { - def actualMessage = actual.message - assert expectedMessage == actualMessage - return; - } - Assert.fail("Expected IllegalArgumentException with message: " + expectedMessage) - } - - @Test public void parse_emptyString() { - assertParse([:], "") - } - - @Test public void parse_emptyMap() { - assertParse([:], "{}") - } - - @Test public void parse_singleString() { - assertParse(["name":"value"], /{"name":"value"}/) - assertParse(["name":"value"], /{"name":"value",}/) - } - - @Test public void parse_singleSquareBracketString() { - assertParse(["name":'"value"'], /{"name":["value"]}/) - assertParse(["name":'"value"'], /{"name":["value"],}/) - assertParse(["error_codes":'50001'], /{"error_codes":[50001]}/) - } - - @Test public void parse_insignificantWhitespace() { - assertParse(["name":"value"], /{"name":"value" ,}/) - assertParse(["name":"value"], /{"name":"value", }/) - assertParse(["name":"value"], /{"name" :"value"}/) - assertParse(["name":"value"], '{\t"name"\r:\t"value"\n}') - } - - @Test public void parse_escapedString() { - assertParse(["name":"/\b\f\"\n\r\t\u20AC\\"], '{"name":"\\/\\b\\f\\"\\n\\r\\t\\u20AC\\\\"}') - } - - @Test public void parse_singleNumber() { - assertParse(["answer":42], /{"answer":42}/) - assertParse(["answer":42], /{"answer":42,}/) - assertParse(["answer":42], /{"answer":42 ,}/) - } - - @Test public void parse_singleNegativeNumber() { - assertParse(["answer":-42], /{"answer":-42}/) - } - - @Test public void parse_singleFractionalNumber() { - assertParse(["answer":4.2], /{"answer":4.2}/) - } - - @Test public void parse_singleExponentialNumber() { - assertParse(["answer":4e1], /{"answer":4e1}/) - assertParse(["answer":4e1], /{"answer":4E1}/) - assertParse(["answer":4e1], /{"answer":4E+1}/) - assertParse(["answer":4e-1], /{"answer":4E-1}/) - } - - @Test public void parse_singleLiteral() { - assertParse(["answer":true], /{"answer":true}/) - assertParse(["answer":true], /{"answer":true,}/) - assertParse(["answer":false], /{"answer":false}/) - assertParse(["answer":false], /{"answer":false,}/) - assertParse(["answer":null], /{"answer":null}/) - assertParse(["answer":null], /{"answer":null,}/) - } - - @Test public void parse_deviceEndpointExampleResponse() { - final input = """ - { - "device_code":"74tq5miHKB", - "user_code":"94248", - "verification_uri":"http://www.example.com/device", - "interval":5 - } -""" - final def expected = [ - "device_code":"74tq5miHKB", - "user_code":"94248", - "verification_uri":"http://www.example.com/device", - "interval":5 - ] - - assertParse(expected, input) - } - - @Test public void parse_commaAfterNumberIsPreKey() { - final input = """ - { - "access_token":"2YotnFZFEjr1zCsicMWpAA", - "token_type":"example", - "expires_in":3600, - "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA", - "example_parameter":"example_value" - } -""" - final def expected = [ - "access_token":"2YotnFZFEjr1zCsicMWpAA", - "token_type":"example", - "expires_in":3600, - "refresh_token":"tGzv3JOkF0XG5Qx2TlKWIA", - "example_parameter":"example_value" - ] - - assertParse(expected, input) - } - - @Test public void parse_error_START() { - assertParseError("Unexpected character '[' at state START.", /[/) - } - - @Test public void parse_error_PRE_KEY() { - assertParseError("Unexpected character '[' at state PRE_KEY.", /{[/) - } - - @Test public void parse_error_PRE_VALUE() { - assertParseError("Unexpected character '=' at state PRE_VALUE.", /{"key"=/) - } - - @Test public void parse_error_VALUE() { - assertParseError("Unexpected character '=' at state VALUE.", /{"key":=/) - } - - @Test public void parse_error_NUMBER_VALUE() { - assertParseError("Unexpected character 'a' at state NUMBER_VALUE.", /{"key":3a/) - } - - @Test public void parse_error_STRING_VALUE_ESCAPE() { - assertParseError("Unexpected character '?' at state STRING_VALUE_ESCAPE.", /{"key":"\?/) - } - - @Test public void parse_error_STRING_VALUE_UNICODE() { - assertParseError("Unexpected character 'x' at state STRING_VALUE_UNICODE.", '{"key":"\\ux') - } - - @Test public void parse_error_LITERAL_VALUE() { - assertParseError("Unexpected character 't' at state LITERAL_VALUE.", /{"key":tt/) - } - - @Test public void parse_error_POST_VALUE() { - assertParseError("Unexpected character ';' at state POST_VALUE.", /{"key":"value";/) - } - - @Test public void parse_error_END() { - assertParseError("Unexpected character ';' at state END.", /{"key":"value"};/) - } -} diff --git a/common/src/test/java/com/microsoft/alm/helpers/BitConverterTest.java b/common/src/test/java/com/microsoft/alm/helpers/BitConverterTest.java deleted file mode 100644 index b040b3fc..00000000 --- a/common/src/test/java/com/microsoft/alm/helpers/BitConverterTest.java +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -import org.junit.Assert; -import org.junit.Test; - -public class BitConverterTest { - @Test - public void toString_example() throws Exception { - final byte[] bytes = {0x7f, 0x2c, 0x4a, 0x00}; - - final String actual = BitConverter.toString(bytes); - - Assert.assertEquals("7F-2C-4A-00", actual); - } -} diff --git a/common/src/test/java/com/microsoft/alm/helpers/GuidTest.java b/common/src/test/java/com/microsoft/alm/helpers/GuidTest.java deleted file mode 100644 index ab2a6ed4..00000000 --- a/common/src/test/java/com/microsoft/alm/helpers/GuidTest.java +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -import org.junit.Assert; -import org.junit.Test; - -import java.util.Arrays; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicReference; - -public class GuidTest { - @Test - public void fromBytes_allOnes() throws Exception { - byte[] input = - { - (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, - (byte) 0xff, (byte) 0xff, - (byte) 0xff, (byte) 0xff, - (byte) 0xff, (byte) 0xff, - (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, - }; - - final UUID actual = Guid.fromBytes(input); - - Assert.assertEquals("ffffffff-ffff-ffff-ffff-ffffffffffff", actual.toString()); - } - - @Test - public void fromBytes_allZeroes() throws Exception { - byte[] input = - { - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, - (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, (byte) 0x00, - }; - - final UUID actual = Guid.fromBytes(input); - - Assert.assertEquals("00000000-0000-0000-0000-000000000000", actual.toString()); - } - - @Test - public void fromBytes_typical() throws Exception { - byte[] input = - { - (byte) 0x3e, (byte) 0x28, (byte) 0x02, (byte) 0x86, - (byte) 0xd6, (byte) 0x2e, - (byte) 0x60, (byte) 0x49, - (byte) 0xad, (byte) 0xaa, - (byte) 0x97, (byte) 0xbe, (byte) 0x7d, (byte) 0x99, (byte) 0x13, (byte) 0xde, - }; - - final UUID actual = Guid.fromBytes(input); - - Assert.assertEquals("8602283e-2ed6-4960-adaa-97be7d9913de", actual.toString()); - } - - @Test - public void toBytes_allOnes() throws Exception { - final UUID value = UUID.fromString("ffffffff-ffff-ffff-ffff-ffffffffffff"); - final byte[] actual = Guid.toBytes(value); - - byte[] expected = - { - (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, - (byte) 0xff, (byte) 0xff, - (byte) 0xff, (byte) 0xff, - (byte) 0xff, (byte) 0xff, - (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, - }; - assertArrayEquals(expected, actual); - } - - @Test - public void toBytes_allZeroes() throws Exception { - final byte[] actual = Guid.toBytes(Guid.Empty); - - byte[] expected = - { - 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, - 0x00, 0x00, - 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - }; - assertArrayEquals(expected, actual); - } - - @Test - public void toBytes_typical() throws Exception { - final UUID value = UUID.fromString("8602283e-2ed6-4960-adaa-97be7d9913de"); - final byte[] actual = Guid.toBytes(value); - - byte[] expected = - { - (byte) 0x3e, (byte) 0x28, (byte) 0x02, (byte) 0x86, - (byte) 0xd6, (byte) 0x2e, - (byte) 0x60, (byte) 0x49, - (byte) 0xad, (byte) 0xaa, - (byte) 0x97, (byte) 0xbe, (byte) 0x7d, (byte) 0x99, (byte) 0x13, (byte) 0xde, - }; - assertArrayEquals(expected, actual); - } - - @Test - public void tryParse_null() throws Exception { - final AtomicReference result = new AtomicReference(); - - final boolean actual = Guid.tryParse(null, result); - - Assert.assertEquals(false, actual); - } - - private static void assertArrayEquals(byte[] expected, byte[] actual) { - if (!Arrays.equals(expected, actual)) { - final String template = "Arrays were different.\n" + - "Expected :%1$s\n" + - "Actual :%2$s"; - final String expectedHex = BitConverter.toString(expected); - final String actualHex = BitConverter.toString(actual); - final String message = String.format(template, expectedHex, actualHex); - Assert.fail(message); - } - } -} diff --git a/common/src/test/java/com/microsoft/alm/helpers/IOHelperTest.java b/common/src/test/java/com/microsoft/alm/helpers/IOHelperTest.java deleted file mode 100644 index 16ce09b3..00000000 --- a/common/src/test/java/com/microsoft/alm/helpers/IOHelperTest.java +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.Random; - -/** - * A class to test {@link IOHelper}. - */ -public class IOHelperTest { - - private static byte[] createRandomByteArray(final int numberOfBytes) { - final Random random = new Random(42); - final byte[] result = new byte[numberOfBytes]; - random.nextBytes(result); - return result; - } - - private static void testCopyStream(final int numberOfBytes) throws IOException { - final byte[] input = createRandomByteArray(numberOfBytes); - final ByteArrayInputStream bais = new ByteArrayInputStream(input); - final ByteArrayOutputStream baos = new ByteArrayOutputStream(numberOfBytes); - - IOHelper.copyStream(bais, baos); - - final byte[] actual = baos.toByteArray(); - Assert.assertArrayEquals(input, actual); - Assert.assertEquals(numberOfBytes, baos.size()); - } - - @Test - public void copyStream_zeroBuffer() throws Exception { - testCopyStream(0); - } - - @Test - public void copyStream_oneByte() throws Exception { - testCopyStream(1); - } - - @Test - public void copyStream_halfSmallerThanBuffer() throws Exception { - testCopyStream(IOHelper.BUFFER_SIZE / 2); - } - - @Test - public void copyStream_justSmallerThanBuffer() throws Exception { - testCopyStream(IOHelper.BUFFER_SIZE - 1); - } - - @Test - public void copyStream_sameAsBuffer() throws Exception { - testCopyStream(IOHelper.BUFFER_SIZE); - } - - @Test - public void copyStream_oneMoreThanBuffer() throws Exception { - testCopyStream(IOHelper.BUFFER_SIZE + 1); - } - - @Test - public void copyStream_twiceMoreThanBuffer() throws Exception { - testCopyStream(IOHelper.BUFFER_SIZE * 2); - } -} diff --git a/common/src/test/java/com/microsoft/alm/helpers/PathTest.java b/common/src/test/java/com/microsoft/alm/helpers/PathTest.java deleted file mode 100644 index d454f2f2..00000000 --- a/common/src/test/java/com/microsoft/alm/helpers/PathTest.java +++ /dev/null @@ -1,104 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -import org.junit.Assert; -import org.junit.Test; - -import java.io.File; -import java.util.ArrayList; -import java.util.List; - -public class PathTest { - @Test - public void changeExtension_single() { - final String goodFileName = "C:\\mydir\\myfile.com"; - - final String actual = Path.changeExtension(goodFileName, ".old"); - - Assert.assertEquals("C:\\mydir\\myfile.old", actual); - } - - @Test - public void changeExtension_singleWithoutLeadingPeriod() { - final String goodFileName = "C:\\mydir\\myfile.com"; - - final String actual = Path.changeExtension(goodFileName, "old"); - - Assert.assertEquals("C:\\mydir\\myfile.old", actual); - } - - @Test - public void changeExtension_multiple() { - final String goodFileName = "C:\\mydir\\myfile.com.extension"; - - final String actual = Path.changeExtension(goodFileName, ".old"); - - Assert.assertEquals("C:\\mydir\\myfile.com.old", actual); - } - - @Test - public void changeExtension_badFileName() { - final String badFileName = "C:\\mydir\\"; - - final String actual = Path.changeExtension(badFileName, ".old"); - - Assert.assertEquals("C:\\mydir\\.old", actual); - } - - // If extension is null, the returned string contains the contents of path - // with the last period and all characters following it removed. - @Test - public void changeExtension_nullExtensionRemovesIt() { - final String goodFileName = "C:\\mydir\\myfile.com.extension"; - - final String actual = Path.changeExtension(goodFileName, null); - - Assert.assertEquals("C:\\mydir\\myfile.com", actual); - } - - // If extension is an empty string, the returned path string contains the contents of path - // with any characters following the last period removed. - @Test - public void changeExtension_emptyExtensionRemovesIt() { - final String goodFileName = "C:\\mydir\\myfile.com.extension"; - - final String actual = Path.changeExtension(goodFileName, StringHelper.Empty); - - Assert.assertEquals("C:\\mydir\\myfile.com.", actual); - } - - @Test - public void construct_path() { - final String[] goodSegments = new String[]{"Library", "Application Support", "Microsoft"}; - - final String actual = Path.construct(goodSegments); - - Assert.assertEquals("Library" + File.separator - + "Application Support" + File.separator + "Microsoft", actual); - } - - @Test - public void construct_argspath() { - final String actual = Path.construct("Library", "Application Support", "Microsoft"); - - Assert.assertEquals("Library" + File.separator - + "Application Support" + File.separator + "Microsoft", actual); - } - - @Test - public void construct_emptypath() { - final String[] emptySegments = new String[0]; - - String actual = Path.construct(emptySegments); - - Assert.assertEquals("", actual); - - final List emptyLists = new ArrayList(); - - actual = Path.construct(emptyLists.toArray(new String[0])); - - Assert.assertEquals("", actual); - } -} diff --git a/common/src/test/java/com/microsoft/alm/helpers/UriHelperTest.java b/common/src/test/java/com/microsoft/alm/helpers/UriHelperTest.java deleted file mode 100644 index a607dbf6..00000000 --- a/common/src/test/java/com/microsoft/alm/helpers/UriHelperTest.java +++ /dev/null @@ -1,166 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.helpers; - -import org.junit.Assert; -import org.junit.Test; - -import java.net.URI; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - -public class UriHelperTest { - - @Test - public void deserializeParameters_null() throws Exception { - final String input = null; - - final QueryString actual = UriHelper.deserializeParameters(input); - - assertMapEntries(actual); - } - - @Test - public void deserializeParameters_empty() throws Exception { - final String input = ""; - - final QueryString actual = UriHelper.deserializeParameters(input); - - assertMapEntries(actual); - } - - @Test - public void deserializeParameters_firstHasNameOnly() throws Exception { - final String input = "nameOnly"; - - final QueryString actual = UriHelper.deserializeParameters(input); - - assertMapEntries(actual, - "nameOnly", null - ); - } - - @Test - public void deserializeParameters_firstHasNameValue() throws Exception { - final String input = "name=value"; - - final QueryString actual = UriHelper.deserializeParameters(input); - - assertMapEntries(actual, - "name", "value" - ); - } - - @Test - public void deserializeParameters_secondHasNameOnly() throws Exception { - final String input = "name=value&nameOnly"; - - final QueryString actual = UriHelper.deserializeParameters(input); - - assertMapEntries(actual, - "name", "value", - "nameOnly", null - ); - } - - @Test - public void deserializeParameters_typical() throws Exception { - final String input = "resource=a8860e8f-ca7d-4efe-b80d-4affab13d4ba&client_id=f7e11bcd-b50b-4869-ad88-8bdd6cbc8473&response_type=code&redirect_uri=https%3A%2F%2Fexample.com&client-request-id=06ac412b-8cc0-4ca5-b943-d9dc218abee6&prompt=login"; - - final QueryString actual = UriHelper.deserializeParameters(input); - - assertMapEntries(actual, - "resource", "a8860e8f-ca7d-4efe-b80d-4affab13d4ba", - "client_id", "f7e11bcd-b50b-4869-ad88-8bdd6cbc8473", - "response_type", "code", - "redirect_uri", "https://example.com", - "client-request-id", "06ac412b-8cc0-4ca5-b943-d9dc218abee6", - "prompt", "login" - ); - } - - private static void assertMapEntries(final Map actualMap, final String... namesAndValues) { - Assert.assertEquals("I need an even number of names and values", 0, namesAndValues.length % 2); - - final Set> actualEntries = actualMap.entrySet(); - final Iterator> aIt = actualEntries.iterator(); - for (int e = 0; e < namesAndValues.length; e += 2) { - Assert.assertTrue(aIt.hasNext()); - final Map.Entry actualPair = aIt.next(); - - final String index = "At index " + (e / 2); - Assert.assertEquals(index, namesAndValues[e], actualPair.getKey()); - Assert.assertEquals(index, namesAndValues[e + 1], actualPair.getValue()); - } - Assert.assertFalse(aIt.hasNext()); - } - - @Test - public void serializeParameters_firstHasNameOnly() throws Exception { - final Map input = new LinkedHashMap(); - input.put("nameOnly", null); - - final String actual = UriHelper.serializeParameters(input); - - Assert.assertEquals("nameOnly", actual); - } - - @Test - public void serializeParameters_secondHasNameOnly() throws Exception { - final Map input = new LinkedHashMap(); - input.put("name", "value"); - input.put("nameOnly", null); - - final String actual = UriHelper.serializeParameters(input); - - Assert.assertEquals("name=value&nameOnly", actual); - } - - @Test - public void serializeParameters_typical() throws Exception { - final Map input = new LinkedHashMap(); - input.put("resource", "a8860e8f-ca7d-4efe-b80d-4affab13d4ba"); - input.put("client_id", "f7e11bcd-b50b-4869-ad88-8bdd6cbc8473"); - input.put("response_type", "code"); - input.put("redirect_uri", "https://example.com"); - input.put("client-request-id", "06ac412b-8cc0-4ca5-b943-d9dc218abee6"); - input.put("prompt", "login"); - - final String actual = UriHelper.serializeParameters(input); - - Assert.assertEquals("resource=a8860e8f-ca7d-4efe-b80d-4affab13d4ba&client_id=f7e11bcd-b50b-4869-ad88-8bdd6cbc8473&response_type=code&redirect_uri=https%3A%2F%2Fexample.com&client-request-id=06ac412b-8cc0-4ca5-b943-d9dc218abee6&prompt=login", actual); - } - - @Test - public void getFullAccount_tests() throws Exception { - Assert.assertEquals("msft.azure.com/account1", UriHelper.getFullAccount(URI.create("https://msft.azure.com/account1/blah"))); - Assert.assertEquals("azure.com/account1", UriHelper.getFullAccount(URI.create("https://azure.com/account1/blah"))); - Assert.assertEquals("azure.com/account1", UriHelper.getFullAccount(URI.create("https://azure.com/account1/blah/"))); - Assert.assertEquals("AZURE.COM/account1", UriHelper.getFullAccount(URI.create("https://AZURE.COM/account1/blah/"))); - Assert.assertEquals("msft.azure.com", UriHelper.getFullAccount(URI.create("https://msft.azure.com/"))); - Assert.assertEquals("visualstudio.com", UriHelper.getFullAccount(URI.create("https://visualstudio.com/defaultcollection/blah/"))); - Assert.assertEquals("VISUALSTUDIO.COM", UriHelper.getFullAccount(URI.create("https://VISUALSTUDIO.COM/defaultcollection/blah/"))); - Assert.assertEquals("azure.com/mseng", UriHelper.getFullAccount( URI.create("https://mseng@azure.com/mseng/VSOnline/_git/VSO"))); - Assert.assertEquals("AZURE.COM/mseng", UriHelper.getFullAccount( URI.create("https://mseng@AZURE.COM/mseng/"))); - Assert.assertEquals("azure.com/mseng", UriHelper.getFullAccount( URI.create("https://mseng@azure.com"))); - Assert.assertEquals("google.com", UriHelper.getFullAccount( URI.create("https://mseng@google.com"))); - } - - @Test - public void IsOrganization_tests() { - Assert.assertEquals(true, UriHelper.isAzureHost( URI.create("https://msft.azure.com/account1/blah"))); - Assert.assertEquals(true, UriHelper.isAzureHost( URI.create("https://azure.com/account1/blah/"))); - Assert.assertEquals(true, UriHelper.isAzureHost( URI.create("https://azure.com/account1/blah"))); - Assert.assertEquals(true, UriHelper.isAzureHost( URI.create("https://AZURE.COM/account1/blah/"))); - Assert.assertEquals(true, UriHelper.isAzureHost( URI.create("https://msft.azure.com/"))); - Assert.assertEquals(false, UriHelper.isAzureHost( URI.create("https://visualstudio.com/defaultcollection/blah/"))); - Assert.assertEquals(false, UriHelper.isAzureHost( URI.create("https://VISUALSTUDIO.COM/defaultcollection/blah/"))); - Assert.assertEquals(false, UriHelper.isAzureHost( URI.create("https://www.google.com"))); - Assert.assertEquals(true, UriHelper.isAzureHost( URI.create("https://mseng@azure.com/mseng/VSOnline/_git/VSO"))); - Assert.assertEquals(true, UriHelper.isAzureHost( URI.create("https://mseng@AZURE.COM/mseng/VSOnline/_git/VSO"))); - Assert.assertEquals(false, UriHelper.isAzureHost( URI.create("https://mseng@google.com/mseng/VSOnline/_git/VSO"))); - } -} diff --git a/common/src/test/java/com/microsoft/alm/secret/CredentialTest.java b/common/src/test/java/com/microsoft/alm/secret/CredentialTest.java deleted file mode 100644 index e27e5a3d..00000000 --- a/common/src/test/java/com/microsoft/alm/secret/CredentialTest.java +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.secret; - -import com.microsoft.alm.helpers.StringHelperTest; -import com.microsoft.alm.helpers.XmlHelper; -import org.junit.Assert; -import org.junit.Test; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import java.io.ByteArrayInputStream; -import java.util.LinkedHashMap; -import java.util.Map; - -public class CredentialTest { - - @Test - public void xmlSerialization_roundTrip() throws Exception { - final Credential credential = new Credential("douglas.adams", "42"); - final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - final DocumentBuilder builder = dbf.newDocumentBuilder(); - final Document serializationDoc = builder.newDocument(); - - final Element element = credential.toXml(serializationDoc); - - serializationDoc.appendChild(element); - final String actualXmlString = XmlHelper.toString(serializationDoc); - final String expectedXmlString = - "\n" + - "\n" + - " 42\n" + - " douglas.adams\n" + - ""; - StringHelperTest.assertLinesEqual(expectedXmlString, actualXmlString); - - final ByteArrayInputStream bais = new ByteArrayInputStream(actualXmlString.getBytes()); - final Document deserializationDoc = builder.parse(bais); - final Element rootNode = deserializationDoc.getDocumentElement(); - - final Credential actualCredential = Credential.fromXml(rootNode); - - Assert.assertEquals(credential.Username, actualCredential.Username); - Assert.assertEquals(credential.Password, actualCredential.Password); - } - - @Test - public void contributeHeader() throws Exception { - final Credential credential = new Credential("douglas.adams", "42"); - final Map headers = new LinkedHashMap(); - - credential.contributeHeader(headers); - - final String actual = headers.get("Authorization"); - Assert.assertEquals("Basic ZG91Z2xhcy5hZGFtczo0Mg==", actual); - } -} diff --git a/common/src/test/java/com/microsoft/alm/secret/TokenPairTest.java b/common/src/test/java/com/microsoft/alm/secret/TokenPairTest.java deleted file mode 100644 index 032edea0..00000000 --- a/common/src/test/java/com/microsoft/alm/secret/TokenPairTest.java +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.secret; - -import com.microsoft.alm.helpers.StringHelperTest; -import com.microsoft.alm.helpers.XmlHelper; -import org.junit.Assert; -import org.junit.Test; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import java.io.ByteArrayInputStream; - -import static org.junit.Assert.assertEquals; - -public class TokenPairTest { - - @Test - public void xmlSerialization_roundTrip() throws Exception { - final TokenPair tokenPair = - new TokenPair("9297fb18-46d0-4846-97ca-ab8dd3b55729", "d15281b1-03f1-4581-90d3-4527d9cf4147"); - final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - final DocumentBuilder builder = dbf.newDocumentBuilder(); - final Document serializationDoc = builder.newDocument(); - - final Element element = tokenPair.toXml(serializationDoc); - - serializationDoc.appendChild(element); - final String actualXmlString = XmlHelper.toString(serializationDoc); - final String expectedXmlString = - "\n" + - "\n" + - " 9297fb18-46d0-4846-97ca-ab8dd3b55729\n" + - " d15281b1-03f1-4581-90d3-4527d9cf4147\n" + - ""; - StringHelperTest.assertLinesEqual(expectedXmlString, actualXmlString); - - final ByteArrayInputStream bais = new ByteArrayInputStream(actualXmlString.getBytes()); - final Document deserializationDoc = builder.parse(bais); - final Element rootNode = deserializationDoc.getDocumentElement(); - - final TokenPair actualTokenPair = TokenPair.fromXml(rootNode); - - assertEquals(tokenPair.AccessToken.Value, actualTokenPair.AccessToken.Value); - assertEquals(tokenPair.RefreshToken.Value, actualTokenPair.RefreshToken.Value); - } - - @Test - public void accessTokenResponse_RFC6749() { - final String input = - " {\n" + - " \"access_token\":\"2YotnFZFEjr1zCsicMWpAA\",\n" + - " \"token_type\":\"example\",\n" + - " \"expires_in\":3600,\n" + - " \"refresh_token\":\"tGzv3JOkF0XG5Qx2TlKWIA\",\n" + - " \"example_parameter\":\"example_value\"\n" + - " }"; - - final TokenPair actual = new TokenPair(input); - - Assert.assertEquals("2YotnFZFEjr1zCsicMWpAA", actual.AccessToken.Value); - Assert.assertEquals("tGzv3JOkF0XG5Qx2TlKWIA", actual.RefreshToken.Value); - Assert.assertEquals("3600.0", actual.Parameters.get("expires_in")); - Assert.assertEquals("example_value", actual.Parameters.get("example_parameter")); - Assert.assertEquals("example", actual.Parameters.get("token_type")); - } - -} diff --git a/common/src/test/java/com/microsoft/alm/secret/TokenTest.java b/common/src/test/java/com/microsoft/alm/secret/TokenTest.java deleted file mode 100644 index 28252f57..00000000 --- a/common/src/test/java/com/microsoft/alm/secret/TokenTest.java +++ /dev/null @@ -1,138 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.secret; - -import com.microsoft.alm.helpers.BitConverter; -import com.microsoft.alm.helpers.Guid; -import com.microsoft.alm.helpers.StringHelperTest; -import com.microsoft.alm.helpers.XmlHelper; -import org.junit.Assert; -import org.junit.Test; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import java.io.ByteArrayInputStream; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicReference; - -public class TokenTest { - - @Test - public void deserialize_newFormat() { - byte[] input = { - (byte) 0x05, (byte) 0x00, (byte) 0x00, (byte) 0x00, - (byte) 0x3e, (byte) 0x28, (byte) 0x02, (byte) 0x86, - (byte) 0xd6, (byte) 0x2e, - (byte) 0x60, (byte) 0x49, - (byte) 0xad, (byte) 0xaa, - (byte) 0x97, (byte) 0xbe, (byte) 0x7d, (byte) 0x99, (byte) 0x13, (byte) 0xde, - (byte) 0x31, - }; - - assertDeserialize(TokenType.Test, "1", "8602283e-2ed6-4960-adaa-97be7d9913de", input); - } - - @Test - public void deserialize_oldFormat() { - byte[] input = { - (byte) 0x31, - }; - - assertDeserialize(TokenType.Test, "1", Guid.Empty.toString(), input); - } - - private static void assertDeserialize(final TokenType expectedTokenType, final String expectedValue, final String expectedGuid, final byte[] input) { - final AtomicReference tokenReference = new AtomicReference(); - final boolean actualResult = Token.deserialize(input, expectedTokenType, tokenReference); - - Assert.assertTrue(actualResult); - final Token actualToken = tokenReference.get(); - Assert.assertNotNull(actualToken); - Assert.assertEquals(expectedValue, actualToken.Value); - Assert.assertEquals(expectedTokenType, actualToken.Type); - final UUID expectedTargetIdentity = UUID.fromString(expectedGuid); - Assert.assertEquals(expectedTargetIdentity, actualToken.getTargetIdentity()); - } - - @Test - public void serialize_almostAllOnes() { - final Token token = new Token("1", TokenType.Access); - token.targetIdentity = UUID.fromString("ffffffff-ffff-ffff-ffff-ffffffffffff"); - - assertSerialize("01-00-00-00-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-FF-31", token); - } - - @Test - public void serialize_almostAllZeroes() { - final Token token = new Token("0", TokenType.Unknown); - - assertSerialize("00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-30", token); - } - - @Test - public void serialize_typical() { - final Token token = new Token("1", TokenType.Test); - token.targetIdentity = UUID.fromString("8602283e-2ed6-4960-adaa-97be7d9913de"); - - assertSerialize("05-00-00-00-3E-28-02-86-D6-2E-60-49-AD-AA-97-BE-7D-99-13-DE-31", token); - } - - private static void assertSerialize(String expectedHex, Token token) { - final AtomicReference bytesRef = new AtomicReference(); - - final boolean actualResult = Token.serialize(token, bytesRef); - - Assert.assertEquals(true, actualResult); - final byte[] actualBytes = bytesRef.get(); - Assert.assertNotNull(actualBytes); - final String actualHex = BitConverter.toString(actualBytes); - Assert.assertEquals(expectedHex, actualHex); - } - - @Test - public void xmlSerialization_roundTrip() throws Exception { - final Token token = new Token("1", TokenType.Access); - token.targetIdentity = UUID.fromString("ffffffff-ffff-ffff-ffff-ffffffffffff"); - final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - final DocumentBuilder builder = dbf.newDocumentBuilder(); - final Document serializationDoc = builder.newDocument(); - - final Element element = token.toXml(serializationDoc); - - serializationDoc.appendChild(element); - final String actualXmlString = XmlHelper.toString(serializationDoc); - final String expectedXmlString = - "\n" + - "\n" + - " Access\n" + - " 1\n" + - " ffffffff-ffff-ffff-ffff-ffffffffffff\n" + - ""; - StringHelperTest.assertLinesEqual(expectedXmlString, actualXmlString); - - final ByteArrayInputStream bais = new ByteArrayInputStream(actualXmlString.getBytes()); - final Document deserializationDoc = builder.parse(bais); - final Element rootNode = deserializationDoc.getDocumentElement(); - - final Token actualToken = Token.fromXml(rootNode); - - Assert.assertEquals(token.Value, actualToken.Value); - Assert.assertEquals(token.Type, actualToken.Type); - Assert.assertEquals(token.targetIdentity, actualToken.targetIdentity); - } - - @Test(expected = IllegalArgumentException.class) - public void validate_tooLong() { - final int numberOfCharacters = 2048; - final StringBuilder sb = new StringBuilder(numberOfCharacters); - for (int c = 0; c < numberOfCharacters; c++) { - sb.append('0'); - } - final Token token = new Token(sb.toString(), TokenType.Test); - - Token.validate(token); - } -} diff --git a/common/src/test/java/com/microsoft/alm/storage/InsecureFileBackendIT.java b/common/src/test/java/com/microsoft/alm/storage/InsecureFileBackendIT.java deleted file mode 100644 index 39741c4c..00000000 --- a/common/src/test/java/com/microsoft/alm/storage/InsecureFileBackendIT.java +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.storage; - -import org.junit.Assert; -import org.junit.Test; - -import java.io.File; -import java.io.IOException; - -public class InsecureFileBackendIT { - - @Test - public void reload_emptyFile() throws IOException { - File tempFile = null; - try { - tempFile = File.createTempFile(this.getClass().getSimpleName(), null); - Assert.assertEquals(0L, tempFile.length()); - - final InsecureFileBackend cut = new InsecureFileBackend(tempFile); - - Assert.assertEquals(0, cut.Tokens.size()); - Assert.assertEquals(0, cut.Credentials.size()); - } finally { - if (tempFile != null) - tempFile.delete(); - } - } - - @Test - public void save_toFile() throws IOException { - File tempFile = null; - try { - tempFile = File.createTempFile(this.getClass().getSimpleName(), null); - final InsecureFileBackend cut = new InsecureFileBackend(tempFile); - - cut.save(); - - Assert.assertTrue(tempFile.length() > 0); - } finally { - if (tempFile != null) - tempFile.delete(); - } - } - -} \ No newline at end of file diff --git a/common/src/test/java/com/microsoft/alm/storage/InsecureFileBackendTest.java b/common/src/test/java/com/microsoft/alm/storage/InsecureFileBackendTest.java deleted file mode 100644 index 659b72bb..00000000 --- a/common/src/test/java/com/microsoft/alm/storage/InsecureFileBackendTest.java +++ /dev/null @@ -1,115 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.storage; - -import com.microsoft.alm.helpers.IOHelper; -import com.microsoft.alm.secret.Credential; -import com.microsoft.alm.secret.Token; -import com.microsoft.alm.secret.TokenType; -import org.junit.Assert; -import org.junit.Test; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; - -public class InsecureFileBackendTest { - - /** - * {@link InsecureFileBackend#delete(String)} must not throw an exception for an invalid key, - * because when entering incorrect credentials, git will issue an "erase" command on an entry - * that may not actually be there, so we shouldn't panic and instead just calmly carry on. - */ - @Test - public void delete_noMatchingTokenOrCredential() { - final InsecureFileBackend cut = new InsecureFileBackend(null); - - cut.delete("foo"); - } - - @Test - public void fromXml() { - ByteArrayInputStream bais = null; - try { - final String xmlString = - "\n" + - "\n" + - " \n" + - " \n" + - " \n" + - " git:https://server.example.com\n" + - " \n" + - " swordfish\n" + - " j.travolta\n" + - " \n" + - " \n" + - " \n" + - ""; - bais = new ByteArrayInputStream(xmlString.getBytes()); - - final InsecureFileBackend actual = InsecureFileBackend.fromXml(bais); - - Assert.assertNotNull(actual); - Assert.assertEquals(1, actual.Credentials.size()); - final Credential credential = actual.Credentials.get("git:https://server.example.com"); - Assert.assertEquals("swordfish", credential.Password); - Assert.assertEquals("j.travolta", credential.Username); - } finally { - IOHelper.closeQuietly(bais); - } - } - - @Test - public void serialization_instanceToXmlToInstance() { - final InsecureFileBackend input = new InsecureFileBackend(null); - initializeTestData(input); - - final InsecureFileBackend actual = clone(input); - - verifyTestData(actual); - } - - - private static void initializeTestData(final InsecureFileBackend input) { - final Token inputBravo = new Token("42", TokenType.Test); - input.writeToken("alpha", null); - input.writeToken("bravo", inputBravo); - input.writeCredential("charlie", null); - final Credential inputDelta = new Credential("douglas.adams", "42"); - input.writeCredential("delta", inputDelta); - } - - private void verifyTestData(final InsecureFileBackend actual) { - Assert.assertEquals(2, actual.Tokens.size()); - Assert.assertTrue(actual.Tokens.containsKey("alpha")); - final Token actualBravo = actual.Tokens.get("bravo"); - Assert.assertEquals("42", actualBravo.Value); - Assert.assertEquals(TokenType.Test, actualBravo.Type); - Assert.assertFalse(actual.Tokens.containsKey("charlie")); - - Assert.assertEquals(2, actual.Credentials.size()); - Assert.assertTrue(actual.Credentials.containsKey("charlie")); - final Credential actualDelta = actual.Credentials.get("delta"); - Assert.assertEquals("douglas.adams", actualDelta.Username); - Assert.assertEquals("42", actualDelta.Password); - } - - static InsecureFileBackend clone(InsecureFileBackend inputStore) { - ByteArrayOutputStream baos = null; - ByteArrayInputStream bais = null; - try { - baos = new ByteArrayOutputStream(); - - inputStore.toXml(baos); - - final String xmlString = baos.toString(); - - bais = new ByteArrayInputStream(xmlString.getBytes()); - - return InsecureFileBackend.fromXml(bais); - } finally { - IOHelper.closeQuietly(baos); - IOHelper.closeQuietly(bais); - } - } -} \ No newline at end of file diff --git a/core/pom.xml b/core/pom.xml deleted file mode 100644 index 684748e3..00000000 --- a/core/pom.xml +++ /dev/null @@ -1,142 +0,0 @@ - - - 4.0.0 - - com.microsoft.alm - auth-lib-parent - 0.6.5 - - auth-core - jar - - Authentication Library for Visual Studio Team Services and Team Foundation Server - A library for authenticating against VSTS and TFS. - https://java.visualstudio.com/ - - - - - ${basedir}/../common/src/main/resources - ./ - - - ${basedir}/.. - ./ - false - - LICENSE.txt - - - - - - maven-enforcer-plugin - - - enforce-versions - - enforce - - - - - 11 - - - - - - - - maven-compiler-plugin - - 11 - 11 - - - - org.codehaus.gmavenplus - gmavenplus-plugin - - - org.apache.maven.plugins - maven-javadoc-plugin - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - - - - - com.microsoft.alm - auth-common - - - - com.microsoft.alm - oauth2-useragent - - - - com.fasterxml.jackson.core - jackson-databind - - - - org.slf4j - slf4j-api - - - - org.slf4j - slf4j-nop - test - - - - junit - junit - test - - - org.mockito - mockito-core - test - - - com.github.tomakehurst - wiremock - test - - - org.littleshoot - littleproxy - test - - - org.codehaus.groovy - groovy - test - - - diff --git a/core/src/main/java/com/microsoft/alm/auth/Authenticator.java b/core/src/main/java/com/microsoft/alm/auth/Authenticator.java deleted file mode 100644 index aa76415f..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/Authenticator.java +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth; - -import com.microsoft.alm.secret.Credential; -import com.microsoft.alm.secret.Secret; -import com.microsoft.alm.secret.Token; -import com.microsoft.alm.secret.TokenPair; -import com.microsoft.alm.secret.VsoTokenScope; - -import java.net.URI; - -/** - * Authenticator that retrieves credentials against Visual Studio Team Services instances or - * Team Foundation Servers. - */ -public interface Authenticator { - - /** - * Get the type of the authentication provided by this authenticator - * - * Possible types are: - * BasicAuth: - * Username and Password combo - * OAuth2: - * OAuth2 token to access Azure protected resource - * PersonalAccessToken: - * Personal Access Token generated against Visual Studio Team Services - * - * @return String representation of the type - */ - String getAuthType(); - - /** - * Get the converter that maps URIs to secret key names - * - * @return an implementation of {@link com.microsoft.alm.secret.Secret.IUriNameConversion} - */ - Secret.IUriNameConversion getUriToKeyConversion(); - - /** - * Set the converter that maps URIs to secret key names - * - * @param conversion an implementation of {@link com.microsoft.alm.secret.Secret.IUriNameConversion} - */ - void setUriToKeyConversion(final Secret.IUriNameConversion conversion); - - /** - * Checks to see if this authenticator supports returning the authorization data in the form of - * username / password {@link Credential} object - * - * @return {@code true} if this authenticator can retrieve a credential object - * {@code false} otherwise - */ - boolean isCredentialSupported(); - - /** - * Retrieve credential for the specified URI with {@link PromptBehavior} AUTO - * - * @param key - * URI identifies the resource been requested - * - * @return - * Credential that can be used to access the resource - */ - Credential getCredential(final URI key); - - /** - * Retrieve credential for the specified URI with specified {@link PromptBehavior} - * - * @param key - * URI identifies the resource been requested - * @param promptBehavior - * dictates whether we should prompt the user for input or not - * - * @return - * Credential that can be used to access the resource - */ - Credential getCredential(final URI key, final PromptBehavior promptBehavior); - - /** - * Checks to see if this authenticator supports returning the authorization data in the form of - * an OAuth2 token pair, which has one access token and a refresh token - * - * @return {@code true} if this authenticator can retrieve {@link TokenPair} - * {@code false} otherwise - */ - boolean isOAuth2TokenSupported(); - - /** - * Retrieve an OAuth2 {@link TokenPair} token pair (access token / refresh token) from Azure AD - * with {@link PromptBehavior} AUTO - * - * https://msdn.microsoft.com/en-us/library/azure/dn645545.aspx - * - * @return an OAuth2 TokenPair from Azure AD - */ - TokenPair getOAuth2TokenPair(); - - /** - * Retrieve an OAuth2 {@link TokenPair} token pair (access token / refresh token) from Azure AD - * with specified {@link PromptBehavior} - * - * https://msdn.microsoft.com/en-us/library/azure/dn645545.aspx - * - * @param promptBehavior - * dictates whether we should prompt the user for input or not - * - * @return an OAuth2 TokenPair from Azure AD - */ - TokenPair getOAuth2TokenPair(final PromptBehavior promptBehavior); - - /** - * Retrieve an OAuth2 {@link TokenPair} token pair (access token / refresh token) from the tenant that backs - * the target URI from Azure AD with specified {@link PromptBehavior} - * - * https://msdn.microsoft.com/en-us/library/azure/dn645545.aspx - * - * @param uri - * a vsts account url, the retrieved OAuth2 token will be from the same tenant - * @param promptBehavior - * dictates whether we should prompt the user for input or not - * - * @return an OAuth2 TokenPair from Azure AD - */ - TokenPair getOAuth2TokenPair(final URI uri, final PromptBehavior promptBehavior); - - /** - * Checks to see if this authenticator supports get a Personal Access {@link Token} object from - * Visual Studio Team Services - * - * @return {@code true} if this authenticator supports retrieving PAT - * {@code false} otherwise - */ - boolean isPersonalAccessTokenSupported(); - - /** - * @deprecated Global Personal Access Token is going away soon, no replacement as of yet. Please generate - * PAT specific to accounts with {@link #getPersonalAccessToken(URI, VsoTokenScope, String, PromptBehavior)} - * - * Retrieve a global Personal Access {@link Token} that works across all accounts the user owns. - *

- * Favor existing global PAT available from the store unless override from the {@link PromptBehavior}. - *

- * If there are no existing Global PAT, and prompting is allowed, we will generate a PAT with the given - * {@link VsoTokenScope} and display name. - * - * - * @param vsoTokenScope - * If we are generating token, the scope of the newly generated token - * @param patDisplayName - * If we are generating token, the display name of the token - * @param promptBehavior - * dictates whether we should prompt the user for input or not - * - * @return global Personal Access Token - */ - @Deprecated - Token getPersonalAccessToken(final VsoTokenScope vsoTokenScope, final String patDisplayName, - final PromptBehavior promptBehavior); - /** - * Retrieve a Personal Access {@link Token} that works for the specified account URI. - *

- * Favor existing token available from the store unless override from the {@link PromptBehavior}. - *

- * If there are no existing PAT, and prompting is allowed, we will generate a PAT with the given - * {@link VsoTokenScope} and display name. - * - * @param key - * The account URI we will be retrieve PAT for - * @param tokenScope - * If we are generating token, the scope of the newly generated token - * @param patDisplayName - * If we are generating token, the display name of the token - * @param promptBehavior - * dictates whether we should prompt the user for input or not - * - * @return a Personal Access Token scoped to the specified account URI - */ - Token getPersonalAccessToken(final URI key, final VsoTokenScope tokenScope, - final String patDisplayName, final PromptBehavior promptBehavior); - - /** - * Retrieve a Personal Access {@link Token} that works for the specified account URI. - *

- * Favor existing token available from the store unless override from the {@link PromptBehavior}. - *

- * If there are no existing PAT, and prompting is allowed, we will generate a PAT with the given - * {@link VsoTokenScope} and display name. - * - * @param key - * The account URI we will be retrieve PAT for - * @param tokenScope - * If we are generating token, the scope of the newly generated token - * @param patDisplayName - * If we are generating token, the display name of the token - * @param promptBehavior - * dictates whether we should prompt the user for input or not - * @param oauth2Token - * if oauth2Token is not null, use it and do not prompt to login via browser - * - * @return a Personal Access Token scoped to the specified account URI - */ - Token getPersonalAccessToken(final URI key, final VsoTokenScope tokenScope, - final String patDisplayName, final PromptBehavior promptBehavior, - final TokenPair oauth2Token); - - /** - * Sign out globally from this library only. This function does not perform any server calls to sign the - * user out. - * - * @return {@code true} if the global secret is removed from this library - * {@code false} otherwise - */ - boolean signOut(); - - /** - * Sign out from this particular URI from this library only. This function does not perform any server calls to - * sign the user out on the server. - * - * @param key - * Forget the secret stored for the account identified by this URI key - * - * @return {@code true} if the global secret is removed from this library - * {@code false} otherwise - */ - boolean signOut(final URI key); -} diff --git a/core/src/main/java/com/microsoft/alm/auth/BaseAuthenticator.java b/core/src/main/java/com/microsoft/alm/auth/BaseAuthenticator.java deleted file mode 100644 index 80a7d69f..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/BaseAuthenticator.java +++ /dev/null @@ -1,277 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth; - -import com.microsoft.alm.secret.Credential; -import com.microsoft.alm.secret.Secret; -import com.microsoft.alm.secret.Token; -import com.microsoft.alm.secret.TokenPair; -import com.microsoft.alm.secret.VsoTokenScope; -import com.microsoft.alm.helpers.Debug; -import com.microsoft.alm.storage.SecretStore; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.URI; -import java.util.concurrent.atomic.AtomicReference; - -/** - * Abstract authenticator with default implementations - * - * Real authenticator should extend this no op authenticator, and they do not have to implement - * methods that don't make sense to them - */ -public abstract class BaseAuthenticator implements Authenticator { - - private static final Logger logger = LoggerFactory.getLogger(BaseAuthenticator.class); - - protected Secret.IUriNameConversion uriToKeyConversion = Secret.DefaultUriNameConversion; - - @Override - public Secret.IUriNameConversion getUriToKeyConversion() { - return uriToKeyConversion; - } - - @Override - public void setUriToKeyConversion(final Secret.IUriNameConversion conversion) { - uriToKeyConversion = conversion; - } - - @Override - public boolean isCredentialSupported() { - return false; - } - - @Override - public Credential getCredential(final URI key) { - return null; - } - - @Override - public Credential getCredential(final URI key, final PromptBehavior promptBehavior) { - return null; - } - - @Override - public boolean isOAuth2TokenSupported() { - return false; - } - - @Override - public TokenPair getOAuth2TokenPair() { - return null; - } - - @Override - public TokenPair getOAuth2TokenPair(final PromptBehavior promptBehavior) { - return null; - } - - @Override - public TokenPair getOAuth2TokenPair(final URI key, final PromptBehavior promptBehavior) { - return null; - } - - @Override - public boolean isPersonalAccessTokenSupported() { - return false; - } - - @Override - public Token getPersonalAccessToken(final VsoTokenScope tokenScope, final String patDisplayName, - final PromptBehavior promptBehavior) { - return null; - } - - @Override - public Token getPersonalAccessToken(final URI key, final VsoTokenScope tokenScope, - final String patDisplayName, final PromptBehavior promptBehavior) { - return null; - } - - @Override - public Token getPersonalAccessToken(final URI key, final VsoTokenScope tokenScope, - final String patDisplayName, final PromptBehavior promptBehavior, - final TokenPair oauth2Token) { - return null; - } - - @Override - public boolean signOut() { - return false; - } - - @Override - public boolean signOut(final URI uri) { - Debug.Assert(uri != null, "uri cannot be null"); - - logger.debug("Signing out from uri: {}", uri); - final String key = getKey(uri); - Debug.Assert(key != null, "key conversion failed"); - - synchronized (getStore()) { - logger.debug("Deleting secret for {}", key); - return getStore().delete(key); - } - } - - /** - * Keys are separated by name space, which are just the authentication type of this Authentcator - * - * So a PAT key will be different from an OAuth2 Key even for the same URI - * - * @param targetUri - * the URL we are trying to authenticate - * - * @return key used to retrieve and store secrets in a secret store - */ - public String getKey(final URI targetUri) { - logger.debug("Getting secret for uri: {}", targetUri); - return this.uriToKeyConversion.convert(targetUri, getAuthType()); - } - - protected abstract SecretStore getStore(); - - /** - * Common pattern to retrieve a secret from store based on supplied prompt behavior - */ - public static abstract class SecretRetriever { - /** - * Standard synchronized access to store. Extensibility point that - * can be overridden - * - * @param key - * key for that credentials are saved under - * @param store - * a secret store that holds credentials - * - * @return stored secret based on key, nullable - */ - protected E readFromStore(final String key, final SecretStore store) { - synchronized (store) { - return store.get(key); - } - } - - /** - * Basic noop verification of the secret. It is up to each authenticator to define proper - * verification process to assure the validity of the secret. It should return reference to a validated - * secret via the secretHolder parameter. - * - * This is an extensibility point. - * - * @param secret - * The secret to validate - * - * @param secretHolder - * This holder should contain a reference to a valid secret - * - * @return {@code true} if secret is validated; {@code false} otherwise. - */ - protected boolean tryGetValidated(final E secret, AtomicReference secretHolder) { - return true; - } - - /** - * How the secret is generated / retrieved. This is the real work - * - * @return secret - */ - protected abstract E doRetrieve(); - - /** - * Standard storing the secret based on the key - * - * This is an extensibility point. - * - * @param key - * key for that credentials are saved under - * @param store - * a secret store that holds credentials - * - * @param secret - * secret to be saved in the store - */ - protected void store(final String key, final SecretStore store, E secret) { - if (secret != null) { - logger.debug("Storing secret for key: {}.", key); - synchronized (store) { - // could be update - store.delete(key); - store.add(key, secret); - } - } - } - - /** - * The main logic for retrieving a key. - * - * Depending on the {@code PromptBehavior} passed in, we should either prompt the user or - * return null when we couldn't retrieve credential based on the key from the specified store - * - * @param key - * key for that credentials are saved under - * @param store - * a secret store that holds credentials - * @param promptBehavior - * determines whether we should prompt or not if we don't have a credential for the specified key - * - * @return secret - * secret to be saved in the store - */ - public E retrieve(final String key, final SecretStore store, - final PromptBehavior promptBehavior) { - logger.debug("Retrieving secret with key: {}, and prompt behavior: {}.", key, promptBehavior.name()); - - E secret = null; - if (promptBehavior != PromptBehavior.ALWAYS) { - // Not ALWAYS prompt, so let's read from the store for any cached secret - logger.debug("Reading secret from store for key: {}", key); - secret = readFromStore(key, store); - - if (secret != null) { - final AtomicReference secretHolder = new AtomicReference(); - secretHolder.set(secret); - - // Verify this secret is valid - if (tryGetValidated(secret, secretHolder)) { - final E validatedSecret = secretHolder.get(); - - // The secret maybe different now, e.g. we could use the refresh token to generate - // a new Access Token - if (!validatedSecret.equals(secret)) { - store.delete(key); - store.add(key, validatedSecret); - - secret = validatedSecret; - } - } else { - secret = null; - // Remove the invalid secret from store - store.delete(key); - } - } - } - - if (promptBehavior == PromptBehavior.NEVER) { - // NEVER prompt, return what we got from the store and call it done - logger.debug("Returning whatever we retrieved from the store, do not prompt."); - return secret; - } - - if (promptBehavior == PromptBehavior.ALWAYS - || (secret == null && promptBehavior == PromptBehavior.AUTO)) { - // Either ALWAYS prompt, or we don't have any secret cached for this key - // AUTO-retrieves when necessary - logger.debug("Retrieving secret."); - secret = doRetrieve(); - - // Store it so we don't have to retrieve again - store(key, store, secret); - } - - return secret; - } - } -} diff --git a/core/src/main/java/com/microsoft/alm/auth/HttpClientFactory.java b/core/src/main/java/com/microsoft/alm/auth/HttpClientFactory.java deleted file mode 100644 index d7286777..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/HttpClientFactory.java +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth; - -import com.microsoft.alm.auth.oauth.Global; -import com.microsoft.alm.helpers.HttpClient; -import com.microsoft.alm.helpers.HttpClientImpl; - -public class HttpClientFactory { - - public HttpClient createHttpClient() { - return new HttpClientImpl(Global.getUserAgent()); - } -} diff --git a/core/src/main/java/com/microsoft/alm/auth/PromptBehavior.java b/core/src/main/java/com/microsoft/alm/auth/PromptBehavior.java deleted file mode 100644 index 5770043a..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/PromptBehavior.java +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth; - -/** - * Indicates whether this library should automatically prompt only if necessary or whether - * it should prompt regardless of whether there is a cached token. - */ -public enum PromptBehavior { - /** - * Will prompt the user for credentials only when necessary. If a token - * that meets the requirements is already cached then the user will not be prompted. - * - * We do not check for scope requirement, so you may get a token that has different scope from what you specified. - */ - AUTO, - - /** - * The user will be prompted for credentials even if there is a token that meets the requirements - * already in the cache. - */ - ALWAYS, - - /** - * The user will not be prompted for credentials. If prompting is necessary then the request to get a secret - * will fail. - */ - NEVER -} diff --git a/core/src/main/java/com/microsoft/alm/auth/basic/BasicAuthAuthenticator.java b/core/src/main/java/com/microsoft/alm/auth/basic/BasicAuthAuthenticator.java deleted file mode 100644 index 0e8cb540..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/basic/BasicAuthAuthenticator.java +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.basic; - -import com.microsoft.alm.auth.BaseAuthenticator; -import com.microsoft.alm.auth.PromptBehavior; -import com.microsoft.alm.secret.Credential; -import com.microsoft.alm.helpers.Debug; -import com.microsoft.alm.storage.InsecureInMemoryStore; -import com.microsoft.alm.storage.SecretStore; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.URI; - -/** - * This authenticator returns username and password combos. - */ -public class BasicAuthAuthenticator extends BaseAuthenticator { - - private static final Logger logger = LoggerFactory.getLogger(BasicAuthAuthenticator.class); - - private final static String TYPE = "BasicAuth"; - - private final SecretStore store; - private final CredentialPrompt prompter; - - /** - * Create BasicAuthAuthenticator with a in-memory secret store {@link InsecureInMemoryStore} and a Swing based - * credential prompt. - */ - public BasicAuthAuthenticator() { - this(new InsecureInMemoryStore(), new DefaultCredentialPrompt()); - } - - public BasicAuthAuthenticator(final SecretStore store, final CredentialPrompt prompter) { - Debug.Assert(store != null, "store cannot be null"); - Debug.Assert(prompter != null, "prompter cannot be null"); - - this.store = store; - this.prompter = prompter; - } - - /** - * Returns the type of this authenticator - * - * @return type BasicAuth - */ - public String getAuthType() { - return TYPE; - } - - @Override - protected SecretStore getStore() { - return this.store; - } - - /** - * This authetnicator supports return secret in the form of a {@link Credential} object - * - * @return {@code true} - */ - @Override - public boolean isCredentialSupported() { - return true; - } - - @Override - public Credential getCredential(final URI uri) { - logger.debug("Retrieving credential for uri: {}", uri); - return getCredential(uri, PromptBehavior.AUTO); - } - - @Override - public Credential getCredential(final URI uri, final PromptBehavior promptBehavior) { - Debug.Assert(uri != null, "getCrednetial uri key cannot be null"); - Debug.Assert(promptBehavior != null, "getCrednetial promptBehavior cannot be null"); - - logger.debug("Retrieving credential for uri: {} with prompt behavior: {}.", uri, promptBehavior.name()); - - final String key = getKey(uri); - - final SecretRetriever secretRetriever = new SecretRetriever() { - @Override - protected Credential doRetrieve() { - logger.debug("Prompt user for credential for uri: {}", uri); - return prompter.prompt(uri); - } - }; - - return secretRetriever.retrieve(key, getStore(), promptBehavior); - } - -} diff --git a/core/src/main/java/com/microsoft/alm/auth/basic/CredentialPrompt.java b/core/src/main/java/com/microsoft/alm/auth/basic/CredentialPrompt.java deleted file mode 100644 index 8a465652..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/basic/CredentialPrompt.java +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.basic; - -import com.microsoft.alm.secret.Credential; - -import java.net.URI; - -public interface CredentialPrompt { - - /** - * Retrieve a credential object - * - * @param target - * the resource we are trying to manage - * - * @return user entered credential - */ - Credential prompt(final URI target); -} diff --git a/core/src/main/java/com/microsoft/alm/auth/basic/DefaultCredentialPrompt.java b/core/src/main/java/com/microsoft/alm/auth/basic/DefaultCredentialPrompt.java deleted file mode 100644 index bda32a6c..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/basic/DefaultCredentialPrompt.java +++ /dev/null @@ -1,185 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.basic; - -import com.microsoft.alm.secret.Credential; - -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JPanel; -import java.awt.event.WindowAdapter; -import java.awt.event.WindowEvent; -import java.net.URI; -import java.util.concurrent.locks.Condition; -import java.util.concurrent.locks.Lock; -import java.util.concurrent.locks.ReentrantLock; - -/** - * A basic credential prompt implementation in Java Swing. - * - * This class provides a very basic CredentialPrompt implementation in Swing. - * - * For any IDE plugin, please implement you own CredentialPrompt that conforms to IDE standard. - */ -public class DefaultCredentialPrompt implements CredentialPrompt { - - private final Lock lock = new ReentrantLock(); - private final Condition userResponseReceived = lock.newCondition(); - - private Credential userEnteredCredential; - - /** - * Creates new form DefaultCredentialPrompt - */ - public DefaultCredentialPrompt() { - JPanel panel = createJPanel(); - - frame = new JFrame(); - frame.setSize(380, 180); - - frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); - - frame.addWindowListener(new WindowAdapter() { - @Override - public void windowClosed(WindowEvent e) { - // closing the windows is the same as cancelling the operation - jButtonCancelActionPerformed(null); - } - }); - frame.add(panel); - } - - /** - * This method is called from within the constructor to create the panel. - * WARNING: this code was generated by netbeans gui editor. Modify at your own risk. - */ - @SuppressWarnings("unchecked") - private JPanel createJPanel() { - final JPanel jPanel = new JPanel(); - final JLabel jLabel1 = new javax.swing.JLabel(); - final JLabel jLabel2 = new javax.swing.JLabel(); - - jTextFieldUsername = new javax.swing.JTextField(); - jPasswordField = new javax.swing.JPasswordField(); - jButtonOK = new javax.swing.JButton(); - jButtonCancel = new javax.swing.JButton(); - - jLabel1.setText("Username:"); - jLabel2.setText("Password:"); - jButtonOK.setText("OK"); - - jButtonOK.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - jButtonOKActionPerformed(evt); - } - }); - - jButtonCancel.setText("Cancel"); - jButtonCancel.addActionListener(new java.awt.event.ActionListener() { - public void actionPerformed(java.awt.event.ActionEvent evt) { - jButtonCancelActionPerformed(evt); - } - }); - - javax.swing.GroupLayout layout = new javax.swing.GroupLayout(jPanel); - jPanel.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addContainerGap(23, Short.MAX_VALUE) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) - .addComponent(jLabel2) - .addComponent(jLabel1)) - .addGap(18, 18, 18) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(jTextFieldUsername) - .addComponent(jPasswordField, javax.swing.GroupLayout.PREFERRED_SIZE, 265, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 5, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup() - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 180, javax.swing.GroupLayout.PREFERRED_SIZE) - .addComponent(jButtonOK) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED) - .addComponent(jButtonCancel))) - .addGap(18, 18, 18)) - ); - layout.setVerticalGroup( - layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGap(23, 23, 23) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(jLabel1) - .addComponent(jTextFieldUsername, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(18, 18, 18) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(jLabel2) - .addComponent(jPasswordField, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)) - .addGap(18, 18, 18) - .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) - .addComponent(jButtonOK) - .addComponent(jButtonCancel)) - .addContainerGap(14, Short.MAX_VALUE)) - ); - - return jPanel; - } - - private void jButtonCancelActionPerformed(java.awt.event.ActionEvent evt) { - lock.lock(); - try { - userEnteredCredential = null; - userResponseReceived.signal(); - } finally { - lock.unlock(); - } - } - - private void jButtonOKActionPerformed(java.awt.event.ActionEvent evt) { - lock.lock(); - try { - final String username = jTextFieldUsername.getText(); - final String password = String.valueOf(jPasswordField.getPassword()); - userEnteredCredential = new Credential(username, password); - - userResponseReceived.signal(); - } finally { - lock.unlock(); - } - } - - private void showPrompt() { - java.awt.EventQueue.invokeLater(new Runnable() { - public void run() { - frame.setVisible(true); - } - }); - } - - public Credential prompt(URI target) { - /* Create and display the form */ - showPrompt(); - - lock.lock(); - try { - userResponseReceived.awaitUninterruptibly(); - - return userEnteredCredential; - - } finally { - frame.dispose(); - lock.unlock(); - } - } - - // Variables declaration - do not modify - private javax.swing.JButton jButtonCancel; - private javax.swing.JButton jButtonOK; - private javax.swing.JPasswordField jPasswordField; - private javax.swing.JTextField jTextFieldUsername; - - private JFrame frame; - // End of variables declaration -} - diff --git a/core/src/main/java/com/microsoft/alm/auth/oauth/AzureAuthority.java b/core/src/main/java/com/microsoft/alm/auth/oauth/AzureAuthority.java deleted file mode 100644 index fdde848c..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/oauth/AzureAuthority.java +++ /dev/null @@ -1,329 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth; - -import com.microsoft.alm.auth.PromptBehavior; -import com.microsoft.alm.helpers.*; -import com.microsoft.alm.oauth2.useragent.AuthorizationException; -import com.microsoft.alm.oauth2.useragent.AuthorizationResponse; -import com.microsoft.alm.oauth2.useragent.UserAgent; -import com.microsoft.alm.oauth2.useragent.UserAgentImpl; -import com.microsoft.alm.secret.Token; -import com.microsoft.alm.secret.TokenPair; -import com.microsoft.alm.secret.TokenType; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicReference; - -/** - * Interfaces with Azure to perform authentication and identity services. - */ -public class AzureAuthority { - - private static final Logger logger = LoggerFactory.getLogger(AzureAuthority.class); - - /** - * The base URL for logon services in Azure. - */ - public static final String AuthorityHostUrlBase = "https://login.microsoftonline.com"; - - /** - * Common tenant for discovery of real tenant - */ - public static final String CommonTenant = "common"; - - /** - * The common Url for logon services in Azure. - */ - public static final String DefaultAuthorityHostUrl = AuthorityHostUrlBase + "/" + CommonTenant; - - /** - * AzureAuthority with common tenant - */ - public static final AzureAuthority DefaultAzureAuthority = new AzureAuthority(); - - private static final String VSTS_BASE_DOMAIN = "visualstudio.com"; - private static final String VSTS_RESOURCE_TENANT_HEADER = "X-VSS-ResourceTenant"; - - private final UserAgent userAgent; - private final AzureDeviceFlow azureDeviceFlow; - - private String authorityHostUrl; - - /** - * Creates a new {@link AzureAuthority} with the default authority host url. - */ - public AzureAuthority() { - this(DefaultAuthorityHostUrl); - } - - /** - * Creates a new {@link AzureAuthority} with an authority host url. - * - * @param authorityHostUrl Non-default authority host url. - */ - public AzureAuthority(final String authorityHostUrl) { - this(authorityHostUrl, new UserAgentImpl(), new AzureDeviceFlow()); - } - - AzureAuthority(final String authorityHostUrl, final UserAgent userAgent, final AzureDeviceFlow azureDeviceFlow) { - Debug.Assert(UriHelper.isWellFormedUriString(authorityHostUrl), "The authorityHostUrl parameter is invalid."); - Debug.Assert(userAgent != null, "The userAgent parameter is null."); - - this.authorityHostUrl = authorityHostUrl; - this.userAgent = userAgent; - this.azureDeviceFlow = azureDeviceFlow; - } - - static URI createAuthorizationEndpointUri(final String authorityHostUrl, final String resource, final String clientId, - final URI redirectUri, final UserIdentifier userId, final String state, - final PromptBehavior promptBehavior, final String queryParameters) { - final QueryString qs = new QueryString(); - qs.put(OAuthParameter.RESOURCE, resource); - qs.put(OAuthParameter.CLIENT_ID, clientId); - qs.put(OAuthParameter.RESPONSE_TYPE, OAuthParameter.CODE); - qs.put(OAuthParameter.REDIRECT_URI, redirectUri.toString()); - - if (!userId.isAnyUser() - && (userId.getType() == UserIdentifierType.OPTIONAL_DISPLAYABLE_ID - || userId.getType() == UserIdentifierType.REQUIRED_DISPLAYABLE_ID)) { - qs.put(OAuthParameter.LOGIN_HINT, userId.getId()); - } - - if (state != null) { - qs.put(OAuthParameter.STATE, state); - } - - String promptValue = null; - switch (promptBehavior) { - case ALWAYS: - promptValue = PromptValue.LOGIN; - break; - case NEVER: - promptValue = PromptValue.ATTEMPT_NONE; - break; - } - if (promptValue != null) { - qs.put(OAuthParameter.PROMPT, promptValue); - } - - final StringBuilder sb = new StringBuilder(authorityHostUrl); - sb.append("/oauth2/authorize?"); - sb.append(qs.toString()); - if (!StringHelper.isNullOrWhiteSpace(queryParameters)) { - // TODO: 449282: ADAL.NET checks if queryParameters contains any duplicate parameters - int start = (queryParameters.charAt(0) == '&') ? 1 : 0; - sb.append('&').append(queryParameters, start, queryParameters.length()); - } - final URI result; - try { - result = new URI(sb.toString()); - } catch (final URISyntaxException e) { - throw new Error(e); - } - return result; - } - - static URI createTokenEndpointUri(final String authorityHostUrl) { - final StringBuilder sb = new StringBuilder(authorityHostUrl); - sb.append("/oauth2/token"); - final URI result; - try { - result = new URI(sb.toString()); - } catch (final URISyntaxException e) { - throw new Error(e); - } - return result; - } - - static StringContent createTokenRequest(final String resource, final String clientId, final String authorizationCode, - final URI redirectUri, final UUID correlationId) { - final QueryString qs = new QueryString(); - qs.put(OAuthParameter.RESOURCE, resource); - qs.put(OAuthParameter.CLIENT_ID, clientId); - qs.put(OAuthParameter.GRANT_TYPE, OAuthParameter.AUTHORIZATION_CODE); - qs.put(OAuthParameter.CODE, authorizationCode); - qs.put(OAuthParameter.REDIRECT_URI, redirectUri.toString()); - if (correlationId != null && !Guid.Empty.equals(correlationId)) { - qs.put(OAuthParameter.CORRELATION_ID, correlationId.toString()); - qs.put(OAuthParameter.REQUEST_CORRELATION_ID_IN_RESPONSE, "true"); - } - final StringContent result = StringContent.createUrlEncoded(qs); - return result; - } - - static StringContent createTokenRequestByRefreshToken(final String resource, final String clientId, - final Token refreshToken) { - - final QueryString qs = new QueryString(); - - qs.put(OAuthParameter.RESOURCE, resource); - qs.put(OAuthParameter.CLIENT_ID, clientId); - qs.put(OAuthParameter.GRANT_TYPE, OAuthParameter.REFRESH_TOKEN); - qs.put(OAuthParameter.REFRESH_TOKEN, refreshToken.Value); - - final StringContent result = StringContent.createUrlEncoded(qs); - return result; - } - - /** - * Determines if there's the targetUri represents a Visual Studio Team Services account - * backed by Azure Active Directory (AAD). - * - * @param targetUri the resource which the authority protects. - * @return the AAD tenant ID if applicable; {@code null} otherwise. - */ - public static UUID detectTenantId(final URI targetUri) throws IOException { - final AtomicReference tenantId = new AtomicReference(Guid.Empty); - - if (StringHelper.endsWithIgnoreCase(targetUri.getHost(), VSTS_BASE_DOMAIN) || - UriHelper.isAzureHost(targetUri)) { - final HttpClient client = Global.getHttpClientFactory().createHttpClient(); - final String tenant = client.getHeaderField(targetUri, VSTS_RESOURCE_TENANT_HEADER); - - if (!StringHelper.isNullOrWhiteSpace(tenant)) { - if (Guid.tryParse(tenant, tenantId)) { - if (!Guid.Empty.equals(tenantId.get())) { - return tenantId.get(); - } - } - } - } - - return null; - } - - private TokenPair doAcquireToken(final URI tokenEndpoint, final StringContent requestContent) throws IOException { - final HttpClient client = Global.getHttpClientFactory().createHttpClient(); - - final String responseContent = client.getPostResponseText(tokenEndpoint, requestContent); - final TokenPair tokenPair = new TokenPair(responseContent); - - return tokenPair; - } - - /** - * Acquires a {@link TokenPair} from the authority via an interactive user logon - * prompt. - * - * @param clientId Identifier of the client requesting the token. - * @param resource Identifier of the target resource that is the recipient of the requested token. - * @param redirectUri Address to return to upon receiving a response from the authority. - * @param queryParameters Optional: appended as-is to the query string in the HTTP authentication request to the - * authority. - * @return If successful, a {@link TokenPair}; otherwise null. - * @throws AuthorizationException if unable to authenticate and authorize the request - */ - public TokenPair acquireToken(final String clientId, final String resource, - final URI redirectUri, String queryParameters) throws AuthorizationException { - Debug.Assert(!StringHelper.isNullOrWhiteSpace(clientId), "The clientId parameter is null or empty"); - Debug.Assert(!StringHelper.isNullOrWhiteSpace(resource), "The resource parameter is null or empty"); - Debug.Assert(redirectUri != null, "The redirectUri parameter is null"); - Debug.Assert(redirectUri.isAbsolute(), "The redirectUri parameter is not an absolute Uri"); - - logger.debug("AzureAuthority::acquireToken"); - - final UUID correlationId = null; - TokenPair tokens = null; - queryParameters = ObjectExtensions.coalesce(queryParameters, StringHelper.Empty); - - final String authorizationCode = acquireAuthorizationCode(resource, clientId, redirectUri, queryParameters); - if (authorizationCode == null) { - logger.debug(" token acquisition failed."); - return tokens; - } - - try { - final URI tokenEndpoint = createTokenEndpointUri(authorityHostUrl); - final StringContent requestContent = createTokenRequest(resource, clientId, authorizationCode, redirectUri, correlationId); - - tokens = doAcquireToken(tokenEndpoint, requestContent); - logger.debug(" token acquisition succeeded."); - - } catch (final IOException e) { - // TODO: 449248: silently catching the exception here seems horribly wrong - logger.debug(" token acquisition failed."); - logger.debug(" IOException: {}", e); - } - return tokens; - } - - /** - * Acquires an access token from the authority using a previously acquired refresh token. - * - * @param clientId Identifier of the client requesting the token. - * @param resource Identifier of the target resource that is the recipient of the requested token. - * @param refreshToken The {@link Token} of type {@link TokenType#Refresh}. - * @return If successful, a {@link TokenPair}; otherwise null. - */ - public TokenPair acquireTokenByRefreshToken(final String clientId, final String resource, - final Token refreshToken) { - Debug.Assert(!StringHelper.isNullOrWhiteSpace(clientId), "The clientId parameter is null or empty"); - Debug.Assert(!StringHelper.isNullOrWhiteSpace(resource), "The resource parameter is null or empty"); - Debug.Assert(refreshToken != null, "The refreshToken parameter is null"); - - logger.debug("AzureAuthority::acquireTokenByRefreshToken"); - - final URI tokenEndpoint = createTokenEndpointUri(authorityHostUrl); - final StringContent requestContent = createTokenRequestByRefreshToken(resource, clientId, refreshToken); - - try { - final TokenPair tokenPair = doAcquireToken(tokenEndpoint, requestContent); - return tokenPair; - } catch (IOException e) { - // TODO: 449248: silently catching the exception here seems horribly wrong - again - logger.debug(" token acquisition failed."); - logger.debug(" IOException: {}", e); - } - - return null; - } - - public TokenPair acquireToken(final String clientId, final String resource, final URI redirectUri, - final Action callback) throws AuthorizationException { - Debug.Assert(!StringHelper.isNullOrWhiteSpace(clientId), "The clientId parameter is null or empty"); - Debug.Assert(!StringHelper.isNullOrWhiteSpace(resource), "The resource parameter is null or empty"); - Debug.Assert(redirectUri != null, "The redirectUri parameter is null"); - Debug.Assert(callback != null, "The callback parameter is null"); - - logger.debug("AzureAuthority::acquireToken"); - - azureDeviceFlow.setResource(resource); - azureDeviceFlow.setRedirectUri(redirectUri.toString()); - - final URI deviceEndpoint = URI.create(authorityHostUrl + "/oauth2/devicecode"); - final DeviceFlowResponse response = azureDeviceFlow.requestAuthorization(deviceEndpoint, clientId, null); - - callback.call(response); - - final URI tokenEndpoint = createTokenEndpointUri(authorityHostUrl); - final TokenPair tokens = azureDeviceFlow.requestToken(tokenEndpoint, clientId, response); - - logger.debug(" token acquisition succeeded."); - return tokens; - } - - private String acquireAuthorizationCode(final String resource, final String clientId, final URI redirectUri, - final String queryParameters) throws AuthorizationException { - final String expectedState = UUID.randomUUID().toString(); - String authorizationCode = null; - final URI authorizationEndpoint = createAuthorizationEndpointUri(authorityHostUrl, resource, clientId, - redirectUri, UserIdentifier.ANY_USER, expectedState, PromptBehavior.ALWAYS, queryParameters); - final AuthorizationResponse response = userAgent.requestAuthorizationCode(authorizationEndpoint, redirectUri); - authorizationCode = response.getCode(); - // verify that the authorization response gave us the state we sent in the authz endpoint URI - final String actualState = response.getState(); - if (!expectedState.equals(actualState)) { - // the states are somehow different; better to assume malice and ignore the authz code - authorizationCode = null; - } - return authorizationCode; - } - -} diff --git a/core/src/main/java/com/microsoft/alm/auth/oauth/AzureDeviceFlow.java b/core/src/main/java/com/microsoft/alm/auth/oauth/AzureDeviceFlow.java deleted file mode 100644 index 9b361a10..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/oauth/AzureDeviceFlow.java +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth; - -import com.microsoft.alm.helpers.QueryString; - -public class AzureDeviceFlow extends DeviceFlowImpl { - private String resource; - - private String redirectUri; - - public String getResource() { - return resource; - } - - public void setResource(final String resource) { - this.resource = resource; - } - - public String getRedirectUri() { - return redirectUri; - } - - public void setRedirectUri(final String redirectUri) { - this.redirectUri = redirectUri; - } - - @Override - protected void contributeAuthorizationRequestParameters(final QueryString bodyParameters) { - if (resource != null) { - bodyParameters.put(OAuthParameter.RESOURCE, resource); - } - - if (redirectUri != null) { - bodyParameters.put(OAuthParameter.REDIRECT_URI, redirectUri); - } - } - - @Override - protected DeviceFlowResponse buildDeviceFlowResponse(final String responseText) { - return AzureDeviceFlowResponse.fromJson(responseText); - } -} diff --git a/core/src/main/java/com/microsoft/alm/auth/oauth/AzureDeviceFlowResponse.java b/core/src/main/java/com/microsoft/alm/auth/oauth/AzureDeviceFlowResponse.java deleted file mode 100644 index e95e90be..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/oauth/AzureDeviceFlowResponse.java +++ /dev/null @@ -1,38 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth; - -import com.microsoft.alm.helpers.PropertyBag; - -import java.net.URI; - -public class AzureDeviceFlowResponse extends DeviceFlowResponse { - static final String VERIFICATION_URL = "verification_url"; - static final String MESSAGE = "message"; - - private final String message; - - public AzureDeviceFlowResponse(final String deviceCode, final String userCode, final URI verificationUri, final int expiresIn, final int interval, final String message) { - super(deviceCode, userCode, verificationUri, expiresIn, interval); - this.message = message; - } - - public String getMessage() { - return message; - } - - public static AzureDeviceFlowResponse fromJson(final String jsonText) { - final PropertyBag bag = PropertyBag.fromJson(jsonText); - final String deviceCode = (String) bag.get(OAuthParameter.DEVICE_CODE); - final String userCode = (String) bag.get(OAuthParameter.USER_CODE); - final String verificationUriString = (String) bag.get(VERIFICATION_URL); - final URI verificationUri = URI.create(verificationUriString); - final int expiresInSeconds = bag.readOptionalInteger(OAuthParameter.EXPIRES_IN, 600); - final int intervalInSeconds = bag.readOptionalInteger(OAuthParameter.INTERVAL, 5); - final String message = (String) bag.get(MESSAGE); - - final AzureDeviceFlowResponse result = new AzureDeviceFlowResponse(deviceCode, userCode, verificationUri, expiresInSeconds, intervalInSeconds, message); - return result; - } -} diff --git a/core/src/main/java/com/microsoft/alm/auth/oauth/DeviceFlow.java b/core/src/main/java/com/microsoft/alm/auth/oauth/DeviceFlow.java deleted file mode 100644 index dd1ba16d..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/oauth/DeviceFlow.java +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth; - -import com.microsoft.alm.oauth2.useragent.AuthorizationException; -import com.microsoft.alm.secret.TokenPair; - -import java.net.URI; - -/** - * An object that implements part of the OAuth 2.0 Device Flow. - * - * @see - * OAuth 2.0 Device Flow IETF Draft 01 - * - */ -public interface DeviceFlow { - /** - * The client initiates the flow by requesting a set of verification - * codes from the authorization server's device endpoint. - * - * @param deviceEndpoint the authorization server's endpoint capable of issuing - * verification codes, user codes, and verification URLs. - * @param clientId the client identifier as described in Section 2.2 of RFC6749. - * @param scope the scope of the access request as described by - * Section 3.3 of RFC6749. (optional) - * @return a {@link DeviceFlowResponse} representing how the 2nd - * phase of the protocol should proceed. - * - * @see "steps (A) and (B) of the Device Flow" - */ - DeviceFlowResponse requestAuthorization(final URI deviceEndpoint, final String clientId, final String scope); - - /** - * The client polls the authorization server's token endpoint repeatedly - * until the end-user grants or denies the request, or the verification - * code expires. - * - * @param tokenEndpoint the authorization server's - * token endpoint as described in Section 4.1.1 of RFC6749. - * @param clientId the client identifier as described in Section 2.2 of RFC6749. - * @param deviceFlowResponse the response obtained from - * {@link #requestAuthorization(URI, String, String)}. - * @return a pair of tokens. - * @throws AuthorizationException the end-user denied the request, - * or the verification code expired. - * - * @see "steps (E) and (F) of the Device Flow" - */ - TokenPair requestToken(final URI tokenEndpoint, String clientId, final DeviceFlowResponse deviceFlowResponse) throws AuthorizationException; -} diff --git a/core/src/main/java/com/microsoft/alm/auth/oauth/DeviceFlowImpl.java b/core/src/main/java/com/microsoft/alm/auth/oauth/DeviceFlowImpl.java deleted file mode 100644 index 64ccd51c..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/oauth/DeviceFlowImpl.java +++ /dev/null @@ -1,164 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth; - -import com.microsoft.alm.helpers.HttpClient; -import com.microsoft.alm.helpers.HttpResponse; -import com.microsoft.alm.helpers.PropertyBag; -import com.microsoft.alm.helpers.QueryString; -import com.microsoft.alm.helpers.StringContent; -import com.microsoft.alm.helpers.StringHelper; - -import com.microsoft.alm.oauth2.useragent.AuthorizationException; -import com.microsoft.alm.secret.TokenPair; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URI; -import java.util.Calendar; - -public class DeviceFlowImpl implements DeviceFlow { - - private static final Logger logger = LoggerFactory.getLogger(DeviceFlowImpl.class); - - @Override - public DeviceFlowResponse requestAuthorization(final URI deviceEndpoint, final String clientId, final String scope) { - final QueryString bodyParameters = new QueryString(); - bodyParameters.put(OAuthParameter.RESPONSE_TYPE, OAuthParameter.DEVICE_CODE); - bodyParameters.put(OAuthParameter.CLIENT_ID, clientId); - if (!StringHelper.isNullOrEmpty(scope)) { - bodyParameters.put(OAuthParameter.SCOPE, scope); - } - contributeAuthorizationRequestParameters(bodyParameters); - final StringContent requestBody = StringContent.createUrlEncoded(bodyParameters); - - final HttpClient client = Global.getHttpClientFactory().createHttpClient(); - final String responseText; - try { - responseText = client.getPostResponseText(deviceEndpoint, requestBody); - } catch (final IOException e) { - throw new Error(e); - } - - final DeviceFlowResponse result = buildDeviceFlowResponse(responseText); - return result; - } - - /** - * Allows subclasses to augment the request to the device endpoint with additional parameters. - * - * @param bodyParameters the {@link QueryString} to which additional parameters should be added. - */ - protected void contributeAuthorizationRequestParameters(final QueryString bodyParameters) { - // do nothing by default - } - - /** - * Allows subclasses to construct a subclass of {@link DeviceFlowResponse} with extra metadata, etc. - * - * @param responseText the JSON response received from the device endpoint. - * - * @return a {@link DeviceFlowResponse} (or subclass thereof). - */ - protected DeviceFlowResponse buildDeviceFlowResponse(final String responseText) { - return DeviceFlowResponse.fromJson(responseText); - } - - @Override - public TokenPair requestToken(final URI tokenEndpoint, final String clientId, final DeviceFlowResponse deviceFlowResponse) throws AuthorizationException { - final QueryString bodyParameters = new QueryString(); - bodyParameters.put(OAuthParameter.GRANT_TYPE, OAuthParameter.DEVICE_CODE); - bodyParameters.put(OAuthParameter.CODE, deviceFlowResponse.getDeviceCode()); - bodyParameters.put(OAuthParameter.CLIENT_ID, clientId); - contributeTokenRequestParameters(bodyParameters); - final StringContent requestBody = StringContent.createUrlEncoded(bodyParameters); - - final int intervalSeconds = deviceFlowResponse.getInterval(); - int intervalMilliseconds = intervalSeconds * 1000; - final HttpClient client = Global.getHttpClientFactory().createHttpClient(); - String responseText = null; - final Calendar expiresAt = deviceFlowResponse.getExpiresAt(); - - do { - if (deviceFlowResponse.cancelRequestedByUser()) { - throw new AuthorizationException("request_cancelled", "Stop polling for Token.", null, null); - } - - try { - final HttpResponse response = client.getPostResponse(tokenEndpoint, requestBody); - - if (response.status == HttpURLConnection.HTTP_OK) { - responseText = response.responseText; - break; - } - else { - final String errorResponseText = response.errorText; - if (response.status == HttpURLConnection.HTTP_BAD_REQUEST) { - final PropertyBag bag = PropertyBag.fromJson(errorResponseText); - final String errorCode = bag.readOptionalString(OAuthParameter.ERROR_CODE, "unknown_error"); - if (OAuthParameter.ERROR_AUTHORIZATION_PENDING.equals(errorCode)) { - try { - Thread.sleep(intervalMilliseconds); - } catch (final InterruptedException e) { - throw new Error(e); - } - continue; - } - else if (OAuthParameter.ERROR_SLOW_DOWN.equals(errorCode)) { - intervalMilliseconds *= 2; - try { - Thread.sleep(intervalMilliseconds); - } catch (final InterruptedException e) { - throw new Error(e); - } - continue; - } - final String errorDescription = bag.readOptionalString(OAuthParameter.ERROR_DESCRIPTION, null); - final String errorUriString = bag.readOptionalString(OAuthParameter.ERROR_URI, null); - final URI errorUri = errorUriString == null ? null : URI.create(errorUriString); - throw new AuthorizationException(errorCode, errorDescription, errorUri, null); - } - else { - throw new Error("Token endpoint returned HTTP " + response.status + ":\n" + errorResponseText); - } - } - } - catch (final IOException e) { - throw new Error(e); - } - } - while (Calendar.getInstance().compareTo(expiresAt) <= 0); - - if (responseText == null) { - throw new AuthorizationException("code_expired", "The verification code expired.", null, null); - } - final TokenPair tokenPair = buildTokenPair(responseText); - - deviceFlowResponse.setTokenAcquired(); - return tokenPair; - } - - /** - * Allows subclasses to augment the request to the token endpoint with additional parameters. - * - * @param bodyParameters the {@link QueryString} to which additional parameters should be added. - */ - protected void contributeTokenRequestParameters(final QueryString bodyParameters) { - // do nothing by default - } - - /** - * Allows subclasses to construct a subclass of {@link TokenPair} with extra metadata, etc. - * - * @param responseText the JSON response received from the token endpoint. - * - * @return a {@link TokenPair} (or subclass thereof). - */ - protected TokenPair buildTokenPair(final String responseText) { - final TokenPair tokenPair = new TokenPair(responseText); - return tokenPair; - } -} diff --git a/core/src/main/java/com/microsoft/alm/auth/oauth/DeviceFlowResponse.java b/core/src/main/java/com/microsoft/alm/auth/oauth/DeviceFlowResponse.java deleted file mode 100644 index 7d752e96..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/oauth/DeviceFlowResponse.java +++ /dev/null @@ -1,186 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth; - -import com.microsoft.alm.helpers.PropertyBag; - -import java.net.URI; -import java.util.Calendar; -import java.util.concurrent.atomic.AtomicReference; - -/** - * Represents the result of requesting device flow authorization from a Device Endpoint. - * - * @see "Section 3.1 Client Requests Authorization" - */ -public class DeviceFlowResponse { - - /** - * WAITING_FOR_USER: - * This library will continue issue server calls to poll for authentication result - * - * CANCEL_REQUESTED: - * This library will stop making server calls and give up on creating a token with device flow - * - * TOKEN_ACQUIRED: - * This library has received an OAuth2 Token based on Device Flow - */ - enum RESPONSE_STATE { - WAITING_FOR_USER, - CANCEL_REQUESTED, - TOKEN_ACQUIRED - } - - private final AtomicReference state = new AtomicReference(); - - private final String deviceCode; - private final String userCode; - private final URI verificationUri; - private final int expiresIn; - private final Calendar expiresAt; - private final int interval; - - public DeviceFlowResponse(final String deviceCode, final String userCode, final URI verificationUri, final int expiresIn, final int interval) { - this.deviceCode = deviceCode; - this.userCode = userCode; - this.verificationUri = verificationUri; - this.expiresIn = expiresIn; - this.expiresAt = Calendar.getInstance(); - expiresAt.add(Calendar.SECOND, expiresIn); - this.interval = interval; - - this.state.set(RESPONSE_STATE.WAITING_FOR_USER); - } - - public static DeviceFlowResponse fromJson(final String jsonText) { - final PropertyBag bag = PropertyBag.fromJson(jsonText); - final String deviceCode = (String) bag.get(OAuthParameter.DEVICE_CODE); - final String userCode = (String) bag.get(OAuthParameter.USER_CODE); - final String verificationUriString = (String) bag.get(OAuthParameter.VERIFICATION_URI); - final URI verificationUri = URI.create(verificationUriString); - final int expiresInSeconds = bag.readOptionalInteger(OAuthParameter.EXPIRES_IN, 600); - final int intervalInSeconds = bag.readOptionalInteger(OAuthParameter.INTERVAL, 5); - - final DeviceFlowResponse result = new DeviceFlowResponse(deviceCode, userCode, verificationUri, expiresInSeconds, intervalInSeconds); - return result; - } - - /** - * The "Device Verification Code" is defined as - * "A short-lived token representing an authorization session." - * in section 2. - * - * @return a string that is to be supplied to the token endpoint. - */ - public String getDeviceCode() { - return deviceCode; - } - - /** - * The "End-User Verification Code" is defined as - * "A short-lived token which the device displays to the end user, is - * entered by the end-user on the authorization sever, and is thus - * used to bind the device to the end-user." - * in section 2. - * - * @return a string that the resource owner (end-user) - * will type into the user-agent (web browser) - * to link their authentication and authorization - * with the device. - */ - public String getUserCode() { - return userCode; - } - - /** - * The end-user verification URI on the authorization - * server. The URI should be short and easy to remember as end- - * users will be asked to manually type it into their user-agent. - * - * @return the URI that the resource owner (end-user) - * will visit with their user-agent (web browser) - * to complete the device flow process. - */ - public URI getVerificationUri() { - return verificationUri; - } - - /** - * The duration in seconds of the verification code - * lifetime. - * - * @return the number of seconds the resource owner (end-user) - * has to complete the device flow process. - * - */ - public int getExpiresIn() { - return expiresIn; - } - - /** - * The date and time when the verification code will - * no longer be valid. - * - * @return a Calendar representing the instant when the - * resource owner (end-user) will no longer be - * able to complete the device flow process. - * - */ - public Calendar getExpiresAt() { - return expiresAt; - } - - /** - * The minimum amount of time in seconds that the client - * SHOULD wait between polling requests to the token endpoint. - * - * @return the minimum number of seconds the client (application) - * waits before polling the token endpoint. - */ - public int getInterval() { - return interval; - } - - /********************************************************************************************** - * Callback flow controls - **********************************************************************************************/ - /** - * The library will use this method to determine if it should give up polling for login result. - * - * @return true if user has requested to cancel this login session explicitly. - */ - boolean cancelRequestedByUser() { - return this.state.get() == RESPONSE_STATE.CANCEL_REQUESTED; - } - - /** - * The library will call this method after it successfully acquired the token. - */ - void setTokenAcquired() { - this.state.set(RESPONSE_STATE.TOKEN_ACQUIRED); - } - - /** - * Callback can monitor this value and react appropriately. For example, a GUI callback dialog can dispose - * itself once it realizes the token has been acquired. - * - * WARNING: make sure the device flow callback does not block the thread this authentication library is running on. - * Otherwise the library will not start polling and this value will never be set. - * - * @return true if library has successfully acquired the token. - */ - public boolean isTokenAcquired() { - return this.state.get() == RESPONSE_STATE.TOKEN_ACQUIRED; - } - - /** - * This method is used by the callback to indicate user has given up, and we should - * stop polling for result. - * - * This is a best effort operation. User may already logged in. - */ - public void requestCancel() { - this.state.set(RESPONSE_STATE.CANCEL_REQUESTED); - } -} diff --git a/core/src/main/java/com/microsoft/alm/auth/oauth/Global.java b/core/src/main/java/com/microsoft/alm/auth/oauth/Global.java deleted file mode 100644 index 10af029d..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/oauth/Global.java +++ /dev/null @@ -1,45 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth; - -import com.microsoft.alm.auth.HttpClientFactory; - -public final class Global { - - private static HttpClientFactory httpClientFactory = new HttpClientFactory(); - private static String userAgent = null; - - /** - * Creates the correct user-agent string for HTTP calls. - * - * @return The `user-agent` string for "auth-library". - * Example from Windows version: - * java-auth-library (Microsoft Windows NT 6.2.9200.0; Win32NT; x64) CLR/4.0.30319 auth-library/1.0.0 - * Example from Java version: - * java-auth-library (Windows 8.1; 6.3; amd64) Java HotSpot(TM) 64-Bit Server VM/1.8.0_51-b16 auth-library/1.0 - * .0-SNAPSHOT - */ - public static String getUserAgent() { - if (userAgent == null) { - // http://stackoverflow.com/a/6773868/ - final String version = Global.class.getPackage().getImplementationVersion(); - userAgent = String.format("java-auth-library (%1$s; %2$s; %3$s) %4$s/%5$s auth-library/%6$s", - System.getProperty("os.name"), // "Windows Server 2012 R2", "Mac OS X", "Linux" - System.getProperty("os.version"), // "6.3", "10.10.5", "3.19.0-28-generic" - System.getProperty("os.arch"), // "amd64", "x86_64", "amd64" - System.getProperty("java.vm.name"), // "Java HotSpot(TM) 64-Bit Server VM", "OpenJDK 64-Bit Server VM" - System.getProperty("java.runtime.version"), // "1.8.0_60-b27", "1.7.0_71-b14", "1.7.0_79-b14" - version); - } - return userAgent; - } - - public static HttpClientFactory getHttpClientFactory() { - return httpClientFactory; - } - - public static void setHttpClientFactory(final HttpClientFactory _httpClientFactory) { - httpClientFactory = _httpClientFactory; - } -} diff --git a/core/src/main/java/com/microsoft/alm/auth/oauth/OAuth2Authenticator.java b/core/src/main/java/com/microsoft/alm/auth/oauth/OAuth2Authenticator.java deleted file mode 100644 index 3d61d64b..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/oauth/OAuth2Authenticator.java +++ /dev/null @@ -1,368 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth; - -import com.microsoft.alm.auth.BaseAuthenticator; -import com.microsoft.alm.auth.PromptBehavior; -import com.microsoft.alm.auth.oauth.helper.AzureAuthorityProvider; -import com.microsoft.alm.auth.oauth.helper.SwtJarLoader; -import com.microsoft.alm.helpers.Action; -import com.microsoft.alm.helpers.Debug; -import com.microsoft.alm.helpers.HttpClient; -import com.microsoft.alm.helpers.HttpClientImpl; -import com.microsoft.alm.helpers.SettingsHelper; -import com.microsoft.alm.helpers.StringHelper; -import com.microsoft.alm.oauth2.useragent.AuthorizationException; -import com.microsoft.alm.secret.Token; -import com.microsoft.alm.secret.TokenPair; -import com.microsoft.alm.storage.InsecureInMemoryStore; -import com.microsoft.alm.storage.SecretStore; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicReference; - -import static com.microsoft.alm.helpers.LoggingHelper.logError; - -public class OAuth2Authenticator extends BaseAuthenticator { - - private static final Logger logger = LoggerFactory.getLogger(OAuth2Authenticator.class); - - public final static String POPUP_QUERY_PARAM = "display=popup"; - - public final static URI APP_VSSPS_VISUALSTUDIO = URI.create("https://app.vssps.visualstudio.com"); - public final static String MANAGEMENT_CORE_RESOURCE = "https://management.core.windows.net/"; - public final static String VALIDATION_ENDPOINT = APP_VSSPS_VISUALSTUDIO + "/_apis/connectionData"; - public static final String VSTS_RESOURCE = "499b84ac-1321-427f-aa17-267ca6975798"; - - public static final String SWT_PROIVDER_NAME = "StandardWidgetToolkit"; - public static final String JAVAFX_PROVIDER_NAME = "JavaFx"; - - private final static String TYPE = "OAuth2"; - - // oauth2-useragent should expose this property as public property, it shouldn't be exposed from here, - // hence "private" modifier - private static final String USER_AGENT_PROVIDER_PROPERTY_NAME = "userAgentProvider"; - - private final String resource; - private final String clientId; - private final URI redirectUri; - - private final SecretStore store; - - private final OAuth2UseragentValidator oAuth2UseragentValidator; - - private final Action deviceFlowCallback; - - private AzureAuthorityProvider azureAuthorityProvider = new AzureAuthorityProvider(); - - /** - * Get an OAuth2 authenticator - * - * @param clientId - * Registered OAuth2 client id - * @param redirectUrl - * Callback url for the registered client - * @param store - * SecretStore to read and save access token to - * - * @return an OAuth2Authenticator - */ - public static OAuth2Authenticator getAuthenticator(final String clientId, final String redirectUrl, - final SecretStore store) { - logger.debug("Authenticator manages resource: {}", MANAGEMENT_CORE_RESOURCE); - - return new OAuth2AuthenticatorBuilder() - .manage(MANAGEMENT_CORE_RESOURCE) - .withClientId(clientId) - .redirectTo(redirectUrl) - .backedBy(store) - .build(); - } - - /** - * Get an OAuth2 authenticator - * - * @param clientId - * Registered OAuth2 client id - * @param redirectUrl - * Callback url for the registered client - * @param store - * SecretStore to read and save access token to - * @param deviceFlowCallback - * an implementation of {@link Action} to invoke when participating - * in OAuth 2.0 Device Flow, providing the end-user with a URI and a code to use for - * authenticating in an external web browser - * - * @return an OAuth2Authenticator - */ - public static OAuth2Authenticator getAuthenticator(final String clientId, final String redirectUrl, - final SecretStore store, final Action deviceFlowCallback) { - logger.debug("Authenticator manages resource: {}", MANAGEMENT_CORE_RESOURCE); - - return new OAuth2AuthenticatorBuilder() - .manage(MANAGEMENT_CORE_RESOURCE) - .withClientId(clientId) - .redirectTo(redirectUrl) - .backedBy(store) - .withDeviceFlowCallback(deviceFlowCallback) - .build(); - } - - /*default*/ OAuth2Authenticator(final String resource, final String clientId, final URI redirectUri, - final SecretStore store, final OAuth2UseragentValidator oAuth2UseragentValidator, - final Action deviceFlowCallback) { - Debug.Assert(resource != null, "resource cannot be null"); - Debug.Assert(clientId != null, "clientId cannot be null"); - Debug.Assert(redirectUri != null, "redirectUri cannot be null"); - - this.resource = resource; - this.clientId = clientId; - this.redirectUri = redirectUri; - this.oAuth2UseragentValidator = oAuth2UseragentValidator; - this.deviceFlowCallback = deviceFlowCallback; - - logger.debug("Using default SecretStore? {}", store == null); - this.store = store == null ? new InsecureInMemoryStore() : store; - } - - @Override - public String getAuthType() { - return this.TYPE; - } - - @Override - protected SecretStore getStore() { - return this.store; - } - - @Override - public boolean isOAuth2TokenSupported() { - return true; - } - - @Override - public TokenPair getOAuth2TokenPair() { - return getOAuth2TokenPair(APP_VSSPS_VISUALSTUDIO, PromptBehavior.AUTO); - } - - @Override - public TokenPair getOAuth2TokenPair(final PromptBehavior promptBehavior) { - return getOAuth2TokenPair(APP_VSSPS_VISUALSTUDIO, promptBehavior); - } - - @Override - public TokenPair getOAuth2TokenPair(final URI uri, final PromptBehavior promptBehavior) { - Debug.Assert(promptBehavior != null, "getOAuth2TokenPair promptBehavior cannot be null"); - Debug.Assert(uri != null, "getOAuth2TokenPair uri cannot be null"); - - logger.debug("Retrieving OAuth2 TokenPair with prompt behavior: {}", promptBehavior.name()); - - final String key = getKey(APP_VSSPS_VISUALSTUDIO); - - final SecretRetriever secretRetriever = new SecretRetriever() { - - private boolean validateAccessToken(final Token accessToken, final URI validationEndpoint) { - final HttpClient client = Global.getHttpClientFactory().createHttpClient(); - accessToken.contributeHeader(client.getHeaders()); - try { - final String response = client.getGetResponseText(validationEndpoint); - - return true; - } catch (IOException e) { - logger.debug("Validation failed with IOException.", e); - } - - return false; - } - - @Override - protected boolean tryGetValidated(final TokenPair tokenPair, final AtomicReference holder) { - Debug.Assert(tokenPair != null, "TokenPair is null"); - Debug.Assert(holder != null, "Holder is null"); - - final URI validationEndpoint = URI.create(VALIDATION_ENDPOINT); - boolean valid = false; - - if (tokenPair.AccessToken != null && !StringHelper.isNullOrEmpty(tokenPair.AccessToken.Value)) { - logger.debug("Validating stored OAuth2 Access Token..."); - valid = validateAccessToken(tokenPair.AccessToken, validationEndpoint); - } - - if (!valid && tokenPair.RefreshToken != null - && !StringHelper.isNullOrEmpty(tokenPair.RefreshToken.Value)) { - logger.debug("OAuth2 Access Token is not valid, and we have a refresh token, try refreshing..."); - - final TokenPair renewedTokenPair = - getAzureAuthority(uri).acquireTokenByRefreshToken(clientId, resource, tokenPair.RefreshToken); - - if (renewedTokenPair != null - && renewedTokenPair.AccessToken.Value != null - && renewedTokenPair.RefreshToken.Value != null) { - logger.debug("OAuth2 Access Token refreshed successfully."); - valid = true; - holder.set(renewedTokenPair); - } - } - - logger.debug("OAuth2 Access Token is {}.", valid ? "valid" : "invalid."); - return valid; - } - - @Override - protected TokenPair doRetrieve() { - logger.info("Ready to launch browser flow to retrieve oauth2 token."); - - final AtomicReference swtRuntime = new AtomicReference(); - - final String defaultProviderName - = SettingsHelper.getInstance().getProperty(USER_AGENT_PROVIDER_PROPERTY_NAME, JAVAFX_PROVIDER_NAME); - - logger.info("Attempt to use oauth2-useragent provider: {}", defaultProviderName); - - final boolean favorSwtBrowser = defaultProviderName.equals(SWT_PROIVDER_NAME); - final boolean favorDeviceFlow = defaultProviderName.equalsIgnoreCase("none"); - - try { - if (favorSwtBrowser) { - logger.debug("Prefer SWT Browser, download SWT Runtime if it is not available."); - if (oAuth2UseragentValidator.isOnlyMissingRuntimeFromSwtProvider()) { - SwtJarLoader.tryGetSwtJar(swtRuntime); - } - } - - if (!favorDeviceFlow) { - if (oAuth2UseragentValidator.isOAuth2ProviderAvailable() - || (oAuth2UseragentValidator.isOnlyMissingRuntimeFromSwtProvider() - && SwtJarLoader.tryGetSwtJar(swtRuntime))) { - try { - logger.info("Using oauth2-useragent providers to retrieve AAD token."); - return getAzureAuthority(uri).acquireToken(clientId, resource, redirectUri, POPUP_QUERY_PARAM); - } catch (final AuthorizationException e) { - logError(logger, "Failed to launch oauth2-useragent.", e); - // unless we failed with unknown reasons (such as failed to load javafx) we probably should - // just return null - if (!"unknown_error".equalsIgnoreCase(e.getCode())) { - // This error code isn't exposed as a value, so just hardcode this string - return null; - } - } - } - } - } catch (IllegalArgumentException iae) { - if (iae.getMessage().startsWith("Unrecognized version string")) { - // On IBM jvm, oauth2-useragent library is not able to parse the jvm version string - // which is in the form of: "jvmwi3260sr9-20110218_76011". - // This is an expected error on IBM jvm. - logError(logger, "Could not parse JVM version, continue with device flow.", iae); - } else { - // This is not expected, throw it to fail fast. - throw iae; - } - } - - // Fallback to Device Flow if there's a callback and the oauth2-useragent couldn't launch the - // browser properly - if (deviceFlowCallback != null) { - logger.info("Fallback to Device Flow."); - try { - return getAzureAuthority(uri).acquireToken(clientId, resource, redirectUri, deviceFlowCallback); - } catch (final AuthorizationException e) { - logError(logger, "Failed to use the Device Flow authenticator.", e); - } - } - - return null; - } - }; - - return secretRetriever.retrieve(key, getStore(), promptBehavior); - } - - public boolean signOut() { - return super.signOut(APP_VSSPS_VISUALSTUDIO); - } - - // For unit test - /*default*/ void setAzureAuthorityProvider(final AzureAuthorityProvider azureAuthorityProvider) { - this.azureAuthorityProvider = azureAuthorityProvider; - } - - private AzureAuthority getAzureAuthority(final URI uri) { - try { - return this.azureAuthorityProvider.getAzureAuthority(uri); - } catch (final IOException ioe) { - throw new Error(ioe); - } - } - - public static class OAuth2AuthenticatorBuilder { - private String resource; - private String clientId; - private URI redirectUri; - private SecretStore store; - private String tenantId = AzureAuthority.CommonTenant; - private Action deviceFlowCallback; - - public OAuth2AuthenticatorBuilder manage(final String resource) { - Debug.Assert(resource != null, "resource cannot be null"); - this.resource = resource; - return this; - } - - public OAuth2AuthenticatorBuilder withClientId(final UUID clientId) { - return this.withClientId(clientId.toString()); - } - - public OAuth2AuthenticatorBuilder withClientId(final String clientId) { - Debug.Assert(clientId != null, "clientId cannot be null"); - this.clientId = clientId; - return this; - } - - public OAuth2AuthenticatorBuilder redirectTo(final URI redirectUri) { - Debug.Assert(redirectUri != null, "redirectUri cannot be null"); - this.redirectUri = redirectUri; - return this; - } - - public OAuth2AuthenticatorBuilder redirectTo(final String redirectUri) { - return this.redirectTo(URI.create(redirectUri)); - } - - public OAuth2AuthenticatorBuilder backedBy(final SecretStore store) { - Debug.Assert(store != null, "store cannot be null"); - this.store = store; - return this; - } - - public OAuth2AuthenticatorBuilder withDeviceFlowCallback(final Action deviceFlowCallback) { - this.deviceFlowCallback = deviceFlowCallback; - return this; - } - - public OAuth2Authenticator build() { - if (this.clientId == null) { - throw new IllegalStateException("ClientId not set"); - } - - if (this.resource == null) { - throw new IllegalStateException("resource not set"); - } - - if (this.redirectUri == null) { - throw new IllegalStateException("redirectUri not set"); - } - - final OAuth2UseragentValidator oAuth2UseragentValidator = new OAuth2UseragentValidator(); - - return new OAuth2Authenticator(this.resource, this.clientId, this.redirectUri, this.store, - oAuth2UseragentValidator, this.deviceFlowCallback); - } - } -} diff --git a/core/src/main/java/com/microsoft/alm/auth/oauth/OAuth2UseragentValidator.java b/core/src/main/java/com/microsoft/alm/auth/oauth/OAuth2UseragentValidator.java deleted file mode 100644 index f9cd856c..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/oauth/OAuth2UseragentValidator.java +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth; - -import com.microsoft.alm.oauth2.useragent.Provider; -import com.microsoft.alm.oauth2.useragent.ProviderScanner; -import com.microsoft.alm.oauth2.useragent.StandardWidgetToolkitProvider; -import com.microsoft.alm.oauth2.useragent.UserAgentImpl; - -import java.util.List; -import java.util.Map; - -/** - * This class verifies the availability of OAuth2-useragent on the current platform - */ -public class OAuth2UseragentValidator { - - private final ProviderScanner scanner = new UserAgentImpl(); - - /** - * Determines if oauth2 useragent can be used on the current running system. - * - * @return {@code true} if oauth2-useragent can be used 100% positively - * {@code false} with any doubts - */ - public boolean isOAuth2ProviderAvailable() { - // not tests are worthy adding since I don't control this implementation - final Provider provider = scanner.findCompatibleProvider(); - - return provider != null; - } - - public boolean isOnlyMissingRuntimeFromSwtProvider() { - final Map> unmetProviderRequirements = scanner.getUnmetProviderRequirements(); - final List unmetSwtProviderRequirement = unmetProviderRequirements.get(Provider.STANDARD_WIDGET_TOOLKIT); - - if (unmetSwtProviderRequirement != null && unmetSwtProviderRequirement.size() == 1) { - return unmetSwtProviderRequirement.get(0).contains(StandardWidgetToolkitProvider.getDefaultSwtJarPath()); - } - - return false; - } -} diff --git a/core/src/main/java/com/microsoft/alm/auth/oauth/OAuthParameter.java b/core/src/main/java/com/microsoft/alm/auth/oauth/OAuthParameter.java deleted file mode 100644 index bc667f33..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/oauth/OAuthParameter.java +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth; - -class OAuthParameter { - static final String RESPONSE_TYPE = "response_type"; - static final String GRANT_TYPE = "grant_type"; - static final String AUTHORIZATION_CODE = "authorization_code"; - static final String DEVICE_CODE = "device_code"; - static final String USER_CODE = "user_code"; - static final String CLIENT_ID = "client_id"; - static final String REDIRECT_URI = "redirect_uri"; - static final String VERIFICATION_URI = "verification_uri"; - static final String RESOURCE = "resource"; - static final String SCOPE = "scope"; - static final String CODE = "code"; - static final String EXPIRES_IN = "expires_in"; - static final String LOGIN_HINT = "login_hint"; - static final String STATE = "state"; - static final String INTERVAL = "interval"; - static final String REFRESH_TOKEN= "refresh_token"; - - static final String ERROR_CODE = "error"; - static final String ERROR_DESCRIPTION = "error_description"; - static final String ERROR_URI = "error_uri"; - - static final String ERROR_AUTHORIZATION_PENDING = "authorization_pending"; - static final String ERROR_SLOW_DOWN = "slow_down"; - - static final String CORRELATION_ID = "client-request-id"; // correlation id is not standard oauth2 parameter - static final String REQUEST_CORRELATION_ID_IN_RESPONSE = "return-client-request-id"; // not standard oauth2 parameter - static final String PROMPT = "prompt"; // prompt is not standard oauth2 parameter -} diff --git a/core/src/main/java/com/microsoft/alm/auth/oauth/PromptValue.java b/core/src/main/java/com/microsoft/alm/auth/oauth/PromptValue.java deleted file mode 100644 index f26f04ef..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/oauth/PromptValue.java +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth; - -class PromptValue { - static final String LOGIN = "login"; - static final String REFRESH_SESSION = "refresh_session"; - - // The behavior of this value is identical to prompt=none for managed users; However, for federated users, AAD - // redirects to ADFS as it cannot determine in advance whether ADFS can login user silently (e.g. via WIA) or not. - static final String ATTEMPT_NONE = "attempt_none"; -} diff --git a/core/src/main/java/com/microsoft/alm/auth/oauth/UserIdentifier.java b/core/src/main/java/com/microsoft/alm/auth/oauth/UserIdentifier.java deleted file mode 100644 index 7a1e9d78..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/oauth/UserIdentifier.java +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth; - -import com.microsoft.alm.helpers.StringHelper; - -/** - * Contains identifier for a user. - */ -public final class UserIdentifier { - private static final String ANY_USER_ID = "AnyUser"; - /** - * A static instance of {@link UserIdentifier} to represent any user. - */ - public static final UserIdentifier ANY_USER = new UserIdentifier(ANY_USER_ID, UserIdentifierType.UNIQUE_ID); - - private final String id; - private final UserIdentifierType type; - - public UserIdentifier(final String id, final UserIdentifierType type) { - if (StringHelper.isNullOrWhiteSpace(id)) { - throw new IllegalArgumentException("id is null or empty"); - } - this.id = id; - this.type = type; - } - - /** - * @return the type of the {@link UserIdentifier}. - */ - public UserIdentifierType getType() { - return this.type; - } - - /** - * @return id of the {@link UserIdentifier}. - */ - public String getId() { - return this.id; - } - - boolean isAnyUser() { - return this.type == ANY_USER.type && this.id.equals(ANY_USER.id); - } - - String getUniqueId() { - return (!this.isAnyUser() && this.type == UserIdentifierType.UNIQUE_ID) ? this.id : null; - } - - String getDisplayableId() { - return (!this.isAnyUser() && (this.type == UserIdentifierType.OPTIONAL_DISPLAYABLE_ID || this.type == UserIdentifierType.REQUIRED_DISPLAYABLE_ID)) ? this.id : null; - } -} diff --git a/core/src/main/java/com/microsoft/alm/auth/oauth/UserIdentifierType.java b/core/src/main/java/com/microsoft/alm/auth/oauth/UserIdentifierType.java deleted file mode 100644 index cebb2365..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/oauth/UserIdentifierType.java +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth; - -/** - * Indicates the type of {@link UserIdentifier} - */ -public enum UserIdentifierType { - /** - * When a {@link UserIdentifier} of this type is passed in a token acquisition operation, - * the operation is guaranteed to return a token issued for the user with corresponding - * {@link UserIdentifier#getUniqueId()} or fail. - */ - UNIQUE_ID, - - /** - * When a {@link UserIdentifier} of this type is passed in a token acquisition operation, - * the operation restricts cache matches to the value provided and injects it as a hint in the - * authentication experience. However the end user could overwrite that value, resulting in a token - * issued to a different account than the one specified in the {@link UserIdentifier} in input. - */ - OPTIONAL_DISPLAYABLE_ID, - - /** - * When a {@link UserIdentifier} of this type is passed in a token acquisition operation, - * the operation is guaranteed to return a token issued for the user with corresponding - * {@link UserIdentifier#getDisplayableId()} (UPN or email) or fail - */ - REQUIRED_DISPLAYABLE_ID,; -} diff --git a/core/src/main/java/com/microsoft/alm/auth/oauth/helper/AzureAuthorityProvider.java b/core/src/main/java/com/microsoft/alm/auth/oauth/helper/AzureAuthorityProvider.java deleted file mode 100644 index f33b9c4a..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/oauth/helper/AzureAuthorityProvider.java +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth.helper; - -import com.microsoft.alm.auth.oauth.AzureAuthority; -import com.microsoft.alm.auth.oauth.OAuth2Authenticator; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.net.URI; -import java.util.UUID; - -/** - * Provide tenant appropriate AzureAuthority - */ -public class AzureAuthorityProvider { - private static final Logger logger = LoggerFactory.getLogger(AzureAuthorityProvider.class); - - public AzureAuthority getAzureAuthority(final URI uri) throws IOException { - if (uri == OAuth2Authenticator.APP_VSSPS_VISUALSTUDIO) { - return AzureAuthority.DefaultAzureAuthority; - } - - logger.debug("Lookup tenant id for {}", uri); - final UUID tenantId = AzureAuthority.detectTenantId(uri); - logger.debug("tenant id for {} is {}", uri, tenantId); - if (tenantId == null) { - // backed by MSA account - return AzureAuthority.DefaultAzureAuthority; - } - - return new AzureAuthority(AzureAuthority.AuthorityHostUrlBase + "/" + tenantId); - } - -} diff --git a/core/src/main/java/com/microsoft/alm/auth/oauth/helper/FileHashHelper.java b/core/src/main/java/com/microsoft/alm/auth/oauth/helper/FileHashHelper.java deleted file mode 100644 index 7949f1b7..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/oauth/helper/FileHashHelper.java +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth.helper; - -import com.microsoft.alm.helpers.IOHelper; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.zip.CRC32; - -/** - * Utility to calculate File hashes. - */ -public class FileHashHelper { - - /** - * Calculates CRC32 hash of a given file. - * - * Warning: CRC32 is not a secure hash algorithm. - * - * @param f any file - * @return CRC32 hash of the file - * @throws IOException - */ - public static long crc32Hash(final File f) throws IOException { - if (!f.isFile()) { - throw new IOException(f.getAbsolutePath() + " is not a valid file."); - } - - final FileInputStream fis = new FileInputStream(f); - final long hash = crc32Hash(fis); - IOHelper.closeQuietly(fis); - - return hash; - } - - /** - * Calculate CRC32 hash of a given input stream. - * - * Warning: CRC32 is not a secure hash algorithm. - * - * @param is a stream of bytes - * @return CRC32 hash of this stream - * @throws IOException - */ - public static long crc32Hash(final InputStream is) throws IOException { - if (is == null) { - throw new IOException("InputStream is null."); - } - - final CRC32 crcMaker = new CRC32(); - final byte[] buffer = new byte[65536]; - int bytesRead; - while((bytesRead = is.read(buffer)) != -1) { - crcMaker.update(buffer, 0, bytesRead); - } - - return crcMaker.getValue(); - } -} diff --git a/core/src/main/java/com/microsoft/alm/auth/oauth/helper/SwtJarLoader.java b/core/src/main/java/com/microsoft/alm/auth/oauth/helper/SwtJarLoader.java deleted file mode 100644 index 48e90bcf..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/oauth/helper/SwtJarLoader.java +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth.helper; - -import com.microsoft.alm.helpers.IOHelper; -import com.microsoft.alm.helpers.SystemHelper; -import com.microsoft.alm.oauth2.useragent.StandardWidgetToolkitProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URL; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; - -public class SwtJarLoader { - private static final Logger logger = LoggerFactory.getLogger(SwtJarLoader.class); - - private static final String BASE_URL = "https://az771546.vo.msecnd.net/swt-binary-for-auth-library/"; - - private static String jarName; - private static File targetSwtJar; - - private static String SWT_VERSION="4.4.2"; - - static final Map CRC32_HASHES; - - static { - boolean isWindows = SystemHelper.isWindows(); - boolean isMac = SystemHelper.isMac(); - boolean isLinux = SystemHelper.isLinux(); - - boolean isx64 = System.getProperty("os.arch").contains("64"); - - jarName = getJarName(isWindows, isLinux, isMac, isx64); - - targetSwtJar = new File(StandardWidgetToolkitProvider.getDefaultSwtJarPath()); - - Map hashes = new HashMap(); - - //CRC32 Hashes of 4.4.2 SWT jar - hashes.put("org.eclipse.swt.cocoa.macosx.x86-4.4.2.jar", 2804720395L); - hashes.put("org.eclipse.swt.cocoa.macosx.x86_64-4.4.2.jar", 3069467037L); - hashes.put("org.eclipse.swt.gtk.linux.x86-4.4.2.jar", 466147888L); - hashes.put("org.eclipse.swt.gtk.linux.x86_64-4.4.2.jar", 3777958147L); - hashes.put("org.eclipse.swt.win32.win32.x86-4.4.2.jar", 2366837566L); - hashes.put("org.eclipse.swt.win32.win32.x86_64-4.4.2.jar", 3238843570L); - - CRC32_HASHES = Collections.unmodifiableMap(hashes); - } - - static String getJarName(final boolean isWindows, final boolean isLinux, final boolean isMac, final boolean isx64) { - /** - *this name should finally look like one of those: - * org.eclipse.swt.cocoa.macosx.x86-4.4.2.jar - * org.eclipse.swt.cocoa.macosx.x86_64-4.4.2.jar - * org.eclipse.swt.gtk.linux.x86-4.4.2.jar - * org.eclipse.swt.gtk.linux.x86_64-4.4.2.jar - * org.eclipse.swt.win32.win32.x86-4.4.2.jar - * org.eclipse.swt.win32.win32.x86_64-4.4.2.jar - */ - final String jarName = "org.eclipse.swt." + - (isWindows ? "win32.win32" : - isMac ? "cocoa.macosx" : - isLinux ? "gtk.linux" : "") + - (isx64 ? ".x86_64-" : ".x86-") + - SWT_VERSION + - ".jar"; - - return jarName; - } - - public static boolean tryGetSwtJar(final AtomicReference swtJarReference) { - //precondition: swt runtime jar is not present on the system - final String swtJarUrl = BASE_URL + jarName; - logger.info("Downloading {}", swtJarUrl); - - try { - final HttpURLConnection cloudSwtUrlConn = (HttpURLConnection) new URL(swtJarUrl).openConnection(); - final int statusCode = cloudSwtUrlConn.getResponseCode(); - if (statusCode != 200) { - throw new IOException(String.format("Failed to download SWT Runtime jar from %s. Server return code is " + - "%d", swtJarUrl, statusCode)); - } - - // Make sure the parent folder exists - final File parent = targetSwtJar.getParentFile(); - if (parent != null && !parent.exists()) { - parent.mkdirs(); - } - - targetSwtJar.createNewFile(); - final FileOutputStream fos = new FileOutputStream(targetSwtJar); - final InputStream is = cloudSwtUrlConn.getInputStream(); - - IOHelper.copyStream(is, fos); - - IOHelper.closeQuietly(is); - IOHelper.closeQuietly(fos); - - if (isValid(targetSwtJar)) { - swtJarReference.set(targetSwtJar); - return true; - } else { - // if target jar is corrupted, cleanup - cleanup(targetSwtJar); - } - - } catch (IOException ioe) { - logger.warn("Failed to download SWT Runtime jar.", ioe); - // if we failed during downloading, remove partial file - cleanup(targetSwtJar); - } - - swtJarReference.set(null); - return false; - } - - private static void cleanup(final File target) { - if(target.exists()) { - target.delete(); - } - } - - private static boolean isValid(final File swtJar) { - try { - long hash = CRC32_HASHES.get(jarName); - - // This only checks the file exists, and assert download didn't corrupt the file. - return swtJar.isFile() && FileHashHelper.crc32Hash(swtJar) == hash; - } catch (IOException e) { - logger.error("Failed to calculate CRC32 Hash of {}", swtJar, e); - } - - return false; - } - -} diff --git a/core/src/main/java/com/microsoft/alm/auth/pat/VsoAzureAuthority.java b/core/src/main/java/com/microsoft/alm/auth/pat/VsoAzureAuthority.java deleted file mode 100644 index 1134635f..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/pat/VsoAzureAuthority.java +++ /dev/null @@ -1,254 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.pat; - -import com.microsoft.alm.auth.oauth.AzureAuthority; -import com.microsoft.alm.auth.oauth.Global; -import com.microsoft.alm.helpers.Debug; -import com.microsoft.alm.helpers.Guid; -import com.microsoft.alm.helpers.HttpClient; -import com.microsoft.alm.helpers.StringContent; -import com.microsoft.alm.helpers.StringHelper; -import com.microsoft.alm.helpers.UriHelper; -import com.microsoft.alm.secret.Token; -import com.microsoft.alm.secret.TokenType; -import com.microsoft.alm.secret.VsoTokenScope; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.net.URI; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicReference; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -class VsoAzureAuthority extends AzureAuthority { - - private static final Logger logger = LoggerFactory.getLogger(VsoAzureAuthority.class); - - /** - * The maximum wait time for a network request before timing out - */ - public static final int RequestTimeout = 15 * 1000; // 15 second limit - - private final static String ALL_ACCOUNTS = "all_accounts"; - - /** - * Generates a personal access token for use with Visual Studio Online. - * - * @param targetUri The uniform resource indicator of the resource access tokens are being requested for. - * @param accessToken - * @param tokenScope - * @param requireCompactToken - * @return - */ - public Token generatePersonalAccessToken(final URI targetUri, final Token accessToken, - final VsoTokenScope tokenScope, final boolean requireCompactToken, - final boolean shouldCreateGlobalToken, final String displayName) { - - Debug.Assert(targetUri != null, "The targetUri parameter is null"); - Debug.Assert(accessToken != null && !StringHelper.isNullOrWhiteSpace(accessToken.Value) && (accessToken.Type == TokenType.Access || accessToken.Type == TokenType.Federated), "The accessToken parameter is null or invalid"); - Debug.Assert(tokenScope != null, "The tokenScope parameter is invalid"); - - logger.debug("VsoAzureAuthority::generatePersonalAccessToken"); - - try { - // TODO: 449524: create a `HttpClient` with a minimum number of redirects, default creds, and a reasonable timeout (access token generation seems to hang occasionally) - final HttpClient client = Global.getHttpClientFactory().createHttpClient(); - logger.debug(" using token to acquire personal access token"); - accessToken.contributeHeader(client.getHeaders()); - - if (shouldCreateGlobalToken || populateTokenTargetId(targetUri, accessToken)) { - final URI requestUrl = createPersonalAccessTokenRequestUri(client, targetUri, requireCompactToken); - - final StringContent content = getAccessTokenRequestBody(accessToken, tokenScope, - shouldCreateGlobalToken, displayName); - - final String responseText = client.getPostResponseText(requestUrl, content); - - final Token token = parsePersonalAccessTokenFromJson(responseText); - if (token != null) { - logger.debug(" personal access token acquisition succeeded."); - } - - return token; - } - } catch (IOException e) { - throw new Error(e); - } - return null; - } - - private URI createPersonalAccessTokenRequestUri(final HttpClient client, final URI targetUri, - final boolean requireCompactToken) throws IOException { - final String SessionTokenUrl = "_apis/token/sessiontokens?api-version=1.0"; - final String CompactTokenUrl = SessionTokenUrl + "&tokentype=compact"; - - Debug.Assert(client != null, "The client is null"); - - final URI identityServiceUri = getIdentityServiceUri(client, targetUri); - if (identityServiceUri == null) { - throw new RuntimeException("Failed to find Identity Service for " + targetUri.toString()); - } - - String url = identityServiceUri.toString(); - - if (!url.endsWith("/")) { - url += "/"; - } - - url += requireCompactToken ? CompactTokenUrl : SessionTokenUrl; - - return URI.create(url); - } - - private URI getIdentityServiceUri(final HttpClient client, final URI targetUri) throws IOException { - final String locationServiceUrlFormat = "https://%1$s/_apis/ServiceDefinitions/LocationService2/951917AC-A960-4999-8464-E3F0AA25B381?api-version=1.0"; - - Debug.Assert(client != null, ("The client parameter is null.")); - Debug.Assert(targetUri != null && targetUri.isAbsolute(), "The targetUri parameter is null or invalid"); - - String host = UriHelper.getFullAccount(targetUri); - - final String locationServiceUrl = String.format(locationServiceUrlFormat, host); - URI identityServiceUri = null; - - final String responseText = client.getGetResponseText(URI.create(locationServiceUrl)); - identityServiceUri = parseLocationFromJson(responseText); - if (identityServiceUri != null) { - logger.debug(" parsed identity service url: {}", identityServiceUri); - } - - return identityServiceUri; - } - - public boolean populateTokenTargetId(final URI targetUri, final Token accessToken) { - Debug.Assert(targetUri != null && targetUri.isAbsolute(), "The targetUri parameter is null or invalid"); - Debug.Assert(accessToken != null && !StringHelper.isNullOrWhiteSpace(accessToken.Value) - && (accessToken.Type == TokenType.Access || accessToken.Type == TokenType.Federated), - "The accessToken parameter is null or invalid"); - - logger.debug("VsoAzureAuthority::populateTokenTargetId"); - - String resultId = null; - try { - // request to the VSO deployment data end-point - final String content = readConnectionDataRequest(targetUri, accessToken); - - - resultId = parseInstanceIdFromJson(content); - } catch (final IOException e) { - logger.debug(" server returned " + e.getMessage()); - } - - final AtomicReference instanceId = new AtomicReference(); - if (Guid.tryParse(resultId, instanceId)) { - logger.debug(" target identity is " + resultId); - accessToken.setTargetIdentity(instanceId.get()); - - return true; - } - - return false; - } - - private static final Pattern TOKEN_PATTERN = Pattern.compile( - "\"token\"\\s*:\\s*\"([^\"]+)\"", - Pattern.CASE_INSENSITIVE - ); - - static Token parsePersonalAccessTokenFromJson(final String json) { - Token token = null; - if (!StringHelper.isNullOrWhiteSpace(json)) { - // find the 'token : ' portion of the result content, if any - final Matcher matcher = TOKEN_PATTERN.matcher(json); - if (matcher.find()) { - final String tokenValue = matcher.group(1); - token = new Token(tokenValue, TokenType.Personal); - } - } - return token; - } - - private static final Pattern INSTANCE_ID_PATTERN = Pattern.compile( - "\"instanceId\"\\s*:\\s*\"([^\"]+)\"", - Pattern.CASE_INSENSITIVE - ); - - static String parseInstanceIdFromJson(final String json) { - String result = null; - - final Matcher matcher = INSTANCE_ID_PATTERN.matcher(json); - if (matcher.find()) { - result = matcher.group(1); - } - - return result; - } - - private static final Pattern LOCATION_PATTERN = Pattern.compile( - "\"location\"\\s*:\\s*\"([^\"]+)\"", - Pattern.CASE_INSENSITIVE - ); - - static URI parseLocationFromJson(final String json) { - URI locationServiceUri = null; - if (!StringHelper.isNullOrWhiteSpace(json)) { - // find the 'location : ' portion of the result content, if any - final Matcher matcher = LOCATION_PATTERN.matcher(json); - if (matcher.find()) { - final String location = matcher.group(1); - locationServiceUri = URI.create(location); - } - } - return locationServiceUri; - } - - private StringContent getAccessTokenRequestBody(final Token accessToken, final VsoTokenScope tokenScope, - final boolean shouldCreateGlobalToken, final String displayName) { - final String ContentJsonFormat = "{ \"scope\" : \"%1$s\", \"targetAccounts\" : [\"%2$s\"], \"displayName\" : \"%3$s\" }"; - - Debug.Assert(accessToken != null && (accessToken.Type == TokenType.Access || accessToken.Type == TokenType.Federated), "The accessToken parameter is null or invalid"); - Debug.Assert(tokenScope != null, "The tokenScope parameter is null"); - - final String targetIdentity = shouldCreateGlobalToken ? ALL_ACCOUNTS : accessToken.getTargetIdentity().toString(); - logger.debug(" creating access token scoped to '" + tokenScope + "' for '" + targetIdentity + "'"); - - final String jsonContent = String.format(ContentJsonFormat, tokenScope, targetIdentity, displayName); - final StringContent content = StringContent.createJson(jsonContent); - return content; - } - - - private String readConnectionDataRequest(final URI targetUri, final Token token) throws IOException { - Debug.Assert(targetUri != null && targetUri.isAbsolute(), "The targetUri parameter is null or invalid"); - Debug.Assert(token != null && (token.Type == TokenType.Access || token.Type == TokenType.Federated), "The token parameter is null or invalid"); - - logger.debug("VsoAzureAuthority::createConnectionDataRequest"); - - final HttpClient client = Global.getHttpClientFactory().createHttpClient(); - - // create an request to the VSO deployment data end-point - final URI requestUri = createConnectionDataUri(targetUri); - - logger.debug(" validating token"); - token.contributeHeader(client.getHeaders()); - - return client.getGetResponseText(requestUri, RequestTimeout); - } - - private URI createConnectionDataUri(final URI targetUri) { - final String VsoValidationUrlFormat = "https://%1$s/_apis/connectiondata"; - - Debug.Assert(targetUri != null & targetUri.isAbsolute(), "The targetUri parameter is null or invalid"); - - String host = UriHelper.getFullAccount(targetUri); - // create a url to the connection data end-point, it's deployment level and "always on". - final String validationUrl = String.format(VsoValidationUrlFormat, host); - - final URI result = URI.create(validationUrl); - return result; - } -} diff --git a/core/src/main/java/com/microsoft/alm/auth/pat/VstsPatAuthenticator.java b/core/src/main/java/com/microsoft/alm/auth/pat/VstsPatAuthenticator.java deleted file mode 100644 index 19a68276..00000000 --- a/core/src/main/java/com/microsoft/alm/auth/pat/VstsPatAuthenticator.java +++ /dev/null @@ -1,360 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.pat; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.microsoft.alm.auth.BaseAuthenticator; -import com.microsoft.alm.auth.PromptBehavior; -import com.microsoft.alm.auth.oauth.Global; -import com.microsoft.alm.auth.oauth.OAuth2Authenticator; -import com.microsoft.alm.helpers.Debug; -import com.microsoft.alm.helpers.HttpClient; -import com.microsoft.alm.helpers.HttpClientImpl; -import com.microsoft.alm.secret.Token; -import com.microsoft.alm.secret.TokenPair; -import com.microsoft.alm.secret.VsoTokenScope; -import com.microsoft.alm.storage.SecretStore; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.net.HttpURLConnection; -import java.net.URI; -import java.util.List; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicReference; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Authenticator based on Personal Access Token - * - * This authenticator will attempt to reuse PATs found in store without regard to the scopes of the PAT. - * - * If the PAT does not have the correct scope, the only way is to reauth by either {@link #signOut(URI)} or {@link - * PromptBehavior} ALWAYS. - */ -public class VstsPatAuthenticator extends BaseAuthenticator { - - private static final Logger logger = LoggerFactory.getLogger(VstsPatAuthenticator.class); - - private final static String TYPE = "PersonalAccessToken"; - - private final VsoAzureAuthority vsoAzureAuthority; - - private final OAuth2Authenticator vstsOauthAuthenticator; - - private final SecretStore store; - - private final ObjectMapper objectMapper; - - /** - * Create a Personal Access Token Authenticator backed by the OAuth2 app with {@code oauthClientId} and - * {@code oauthClientRedirectUri}. - * - * The oauthTokenStore will be utilized to check if there is valid OAuth2 {@link TokenPair} available first - * - * @param oauthClientId - * registered OAuth2 client id - * @param oauthClientRedirectUrl - * registered OAuth2 client redirect URI - * @param oauthTokenStore - * A secret store that will be used to check for available OAuth2 TokenPair - * @param store - * Store for personal access tokens - */ - public VstsPatAuthenticator(final String oauthClientId, final String oauthClientRedirectUrl, - final SecretStore oauthTokenStore, - final SecretStore store) { - Debug.Assert(oauthClientId!= null, "oauthClientId cannot be null"); - Debug.Assert(oauthClientRedirectUrl!= null, "oauthClientRedirectUrl cannot be null"); - Debug.Assert(store != null, "store cannot be null"); - - this.vstsOauthAuthenticator = OAuth2Authenticator.getAuthenticator(oauthClientId, - oauthClientRedirectUrl, oauthTokenStore); - this.vsoAzureAuthority = new VsoAzureAuthority(); - this.store = store; - this.objectMapper = new ObjectMapper(); - } - - /** - * Create a Personal Access Token Authenticator backed a particular {@link OAuth2Authenticator} - * - * @param oauth2Authenticator - * a fully materialized oauth2 authenticator - * @param store - * Store for personal access tokens - */ - public VstsPatAuthenticator(final OAuth2Authenticator oauth2Authenticator, final SecretStore store) { - this(new VsoAzureAuthority(), oauth2Authenticator, store); - } - - /* default */ VstsPatAuthenticator(final VsoAzureAuthority vsoAzureAuthority, - final OAuth2Authenticator oauth2Authenticator, - final SecretStore store) { - //only those two fields are passed in from outside of this class - Debug.Assert(oauth2Authenticator != null, "oauth2Authenticatorcannot be null"); - Debug.Assert(store != null, "store cannot be null"); - - this.vsoAzureAuthority = vsoAzureAuthority; - this.vstsOauthAuthenticator = oauth2Authenticator; - this.store = store; - this.objectMapper = new ObjectMapper(); - } - - @Override - protected SecretStore getStore() { - return this.store; - } - - @Override - public String getAuthType() { - return TYPE; - } - - @Override - public boolean isPersonalAccessTokenSupported() { - return true; - } - - @Override - public Token getPersonalAccessToken(final VsoTokenScope tokenScope, final String patDisplayName, - final PromptBehavior promptBehavior) { - // Global PAT will be stored with URI key APP_VSSPS_VISUALSTUDIO as this key doesn't identify any account - logger.debug("Retrieving global Personal Access Token."); - return getToken(vstsOauthAuthenticator.APP_VSSPS_VISUALSTUDIO, true, tokenScope, - patDisplayName, promptBehavior, null); - } - - @Override - public Token getPersonalAccessToken(final URI uri, final VsoTokenScope tokenScope, final String patDisplayName, - final PromptBehavior promptBehavior) { - logger.debug("Retrieving Personal Access Token for uri: {}", uri); - return getToken(uri, false, tokenScope, patDisplayName, promptBehavior, null); - } - - @Override - public Token getPersonalAccessToken(final URI uri, final VsoTokenScope tokenScope, final String patDisplayName, - final PromptBehavior promptBehavior, final TokenPair oauth2Token) { - logger.debug("Retrieving Personal Access Token for uri: {}", uri); - return getToken(uri, false, tokenScope, patDisplayName, promptBehavior, oauth2Token); - } - - private Token getToken(final URI uri, final boolean isCreatingGlobalPat, - final VsoTokenScope tokenScope, final String patDisplayName, - final PromptBehavior promptBehavior, final TokenPair oauth2Token) { - Debug.Assert(uri != null, "uri cannot be null"); - Debug.Assert(promptBehavior != null, "promptBehavior cannot be null"); - - logger.info("Retrieving PersonalAccessToken for uri:{} with name:{}, and with scope:{}, prompt behavior: {}", - uri, patDisplayName, tokenScope, promptBehavior.name()); - - final String key = getKey(uri); - Debug.Assert(key != null, "Failed to convert uri to key"); - - final SecretRetriever secretRetriever = new SecretRetriever() { - @Override - protected boolean tryGetValidated(final Token token, final AtomicReference holder) { - Debug.Assert(token != null, "Token is null"); - Debug.Assert(holder != null, "Holder is null"); - - final URI validationEndpoint = URI.create(uri + "/_apis/connectionData"); - boolean valid = false; - - if (token.Value != null) { - final HttpClient client = Global.getHttpClientFactory().createHttpClient(); - token.contributeHeader(client.getHeaders()); - try { - client.getGetResponseText(validationEndpoint); - valid = true; - } catch (IOException e) { - logger.debug("Validation failed with IOException.", e); - } - } - - logger.debug("Personal Access Token is {}.", valid ? "valid" : "invalid."); - return valid; - } - - @Override - protected Token doRetrieve() { - final TokenPair tokenPair = (oauth2Token == null) - ? vstsOauthAuthenticator.getOAuth2TokenPair(uri, promptBehavior.AUTO) - : oauth2Token; - - if (tokenPair == null) { - // authentication failed, return null - logger.debug("Failed to get an OAuth2 token, cannot generate PersonalAccessToken."); - return null; - } - logger.debug("Got OAuth2 token, retrieving Personal Access Token with it."); - - final URI accountSpecificUri = createAccountSpecificUri(uri, tokenPair); - final Token pat = vsoAzureAuthority.generatePersonalAccessToken(accountSpecificUri, tokenPair.AccessToken, - tokenScope, true, isCreatingGlobalPat, patDisplayName); - - return pat; - } - }; - - return secretRetriever.retrieve(key, getStore(), promptBehavior); - } - - private URI createAccountSpecificUri(final URI uri, final TokenPair tokenPair) { - if (vstsOauthAuthenticator.APP_VSSPS_VISUALSTUDIO.equals(uri)) { - logger.debug("Find an account level target url to generate Personal Access Token."); - final HttpClient client = Global.getHttpClientFactory().createHttpClient(); - tokenPair.AccessToken.contributeHeader(client.getHeaders()); - - try { - final String profileId = getProfileId(client); - final String accountUri = getAccountUri(client, profileId); - - logger.debug("Found account: {}", accountUri); - return URI.create(accountUri); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - // no need to translate any other uri - return uri; - } - - private String getProfileId(final HttpClient authenticatedClient) throws IOException { - Debug.Assert(authenticatedClient != null, "authenticatedClient is null"); - - final URI profileUri = URI.create("https://app.vssps.visualstudio.com/_apis/profile/profiles/me?api-version=1.0"); - final HttpURLConnection response; - logger.debug("Getting user profile..."); - final String responseText = authenticatedClient.getGetResponseText(profileUri); - - final String id = parseIdFromJson(responseText); - if (id != null) { - logger.debug("Profile id: {}", id); - return id; - } - - throw new RuntimeException("Failed to get profile id."); - } - - private static final Pattern ID_PATTERN = Pattern.compile( - "\"id\"\\s*:\\s*\"([^\"]+)\"", - Pattern.CASE_INSENSITIVE - ); - - static String parseIdFromJson(final String json) { - String result = null; - - final Matcher matcher = ID_PATTERN.matcher(json); - if (matcher.find()) { - result = matcher.group(1); - } - - return result; - } - - private String getAccountUri(final HttpClient authenticatedClient, final String profileId) throws IOException { - Debug.Assert(authenticatedClient != null, "authenticatedClient is null"); - Debug.Assert(profileId != null, "profileId is null"); - - final String accountApiUrlFormat = "https://app.vssps.visualstudio.com/_apis/Accounts?memberid=%s&api-version=1.0"; - final URI accountApiUrl = URI.create(String.format(accountApiUrlFormat, profileId)); - - final String vstsAccountUrlFormat = "https://%s.visualstudio.com/"; - - logger.debug("Account API URL: {}", accountApiUrl); - - final String content = authenticatedClient.getGetResponseText(accountApiUrl); - - if (content != null) { - final AccountList accountList = this.objectMapper.readValue(content, AccountList.class); - if (accountList != null && accountList.value != null) { - for (final Account account : accountList.value) { - if (account.accountStatus != null && account.accountUri != null) { - return String.format(vstsAccountUrlFormat, account.accountName); - } - } - } - } - - throw new RuntimeException("Could not find any accounts."); - } - - /** - * "Forget" the global PAT, also remove the oauth token to force sign in again - * - * @return {@code true} if global PAT and the OAuth2 token used to generate this PAT are both forgotten - */ - @Override - public boolean signOut() { - return this.signOut(vstsOauthAuthenticator.APP_VSSPS_VISUALSTUDIO); - } - - @Override - public boolean signOut(final URI uri) { - logger.info("Signing out from uri: {}", uri); - Debug.Assert(uri != null, "uri cannot be null"); - - return super.signOut(uri) - && vstsOauthAuthenticator.signOut(); - } - - /** - * @deprecated Global PAT is going away soon - * - * Since global PAT suppose to work across accounts, we can associate the global PAT to a particular account - * and everything should still work. - * - * @param uri - * Target account uri - * - * @return {@code true} if there is a global PAT and we successfully associated it with the target uri - * {@code false} otherwise - */ - public boolean assignGlobalPatTo(final URI uri) { - Debug.Assert(uri != null, "uri cannot be null"); - logger.debug("Assigning the global PAT to uri: {}", uri); - - final String globalKey = getKey(vstsOauthAuthenticator.APP_VSSPS_VISUALSTUDIO); - final Token token = getStore().get(globalKey); - if (token != null) { - assign(uri, token); - logger.debug("Global PAT transferred to uri: {}", uri); - return true; - } else { - logger.debug("Could not find global PAT."); - } - - return false; - } - - private void assign(final URI uri, final Token token) { - final String key = getKey(uri); - getStore().add(key, token); - } - - /** - * Simple data-binding classes for parsing VSTS Accounts from JSON - * - * This class is used in order to avoid a full dependency on VSTS REST Http client - */ - @JsonIgnoreProperties(ignoreUnknown = true) - public static class AccountList { - public int count; - public List value; - } - - @JsonIgnoreProperties(ignoreUnknown = true) - public static class Account { - public UUID accountId; - public URI accountUri; - public String accountName; - public String organizationName; - public String accountType; - public UUID accountOwner; - public String accountStatus; - } -} diff --git a/core/src/test/groovy/com/microsoft/alm/auth/oauth/AzureDeviceFlowResponseTest.groovy b/core/src/test/groovy/com/microsoft/alm/auth/oauth/AzureDeviceFlowResponseTest.groovy deleted file mode 100644 index b5314a4f..00000000 --- a/core/src/test/groovy/com/microsoft/alm/auth/oauth/AzureDeviceFlowResponseTest.groovy +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth - -import groovy.transform.CompileStatic -import org.junit.Test - -/** - * A class to test {@link AzureDeviceFlowResponse}. - */ -@CompileStatic -public class AzureDeviceFlowResponseTest { - - @Test public void fromJson_azureActiveDirectory() { - final now = Calendar.instance - final def input = """\ -{"user_code":"EZ2KYPAW4","device_code":"EAAABAAEAiL9Kn2Z27Uubv","verification_url":"https://aka.ms/devicelogin","expires_in":"900","interval":"5","message":"To sign in, use a web browser to open the page https://aka.ms/devicelogin. Enter the code EZ2KYPAW4 to authenticate."}\ -""" - - final actual = AzureDeviceFlowResponse.fromJson(input) - - assert "EAAABAAEAiL9Kn2Z27Uubv" == actual.deviceCode - assert "EZ2KYPAW4" == actual.userCode - assert URI.create("https://aka.ms/devicelogin") == actual.verificationUri - assert 5 == actual.interval - assert 900 == actual.expiresIn - assert actual.expiresAt.timeInMillis - now.timeInMillis >= 900 - assert "To sign in, use a web browser to open the page https://aka.ms/devicelogin. Enter the code EZ2KYPAW4 to authenticate." == actual.message; - } -} diff --git a/core/src/test/groovy/com/microsoft/alm/auth/oauth/AzureDeviceFlowTest.groovy b/core/src/test/groovy/com/microsoft/alm/auth/oauth/AzureDeviceFlowTest.groovy deleted file mode 100644 index 04052cf7..00000000 --- a/core/src/test/groovy/com/microsoft/alm/auth/oauth/AzureDeviceFlowTest.groovy +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth - -import com.microsoft.alm.secret.TokenPair -import com.microsoft.alm.secret.TokenType -import groovy.transform.CompileStatic -import org.junit.Ignore -import org.junit.Test - -/** - * A class to test {@link AzureDeviceFlow}. - */ -@CompileStatic -public class AzureDeviceFlowTest { - - @Test public void buildTokenPair_sampleResponse() { - final input = /{"token_type":"Bearer","scope":"user_impersonation","expires_in":"3600","expires_on":"1460690464","not_before":"1460686564","resource":"4e725760-015b-4168-8adf-e8329e863974","access_token":"Q29uZ3JhdHVsYXRpb25zLCB5b3UgaGF2ZSBzdWNjZXNzZnVsbHkgZGVjb2RlZCBhIGZha2UgYWNjZXNzIHRva2VuLg==","refresh_token":"Tm93IHlvdSBzdWNjZXNzZnVsbHkgZGVjb2RlZCBhIGZha2UgcmVmcmVzaCB0b2tlbi4=","id_token":"SSBub3RpY2UgeW91J3JlIHN0aWxsIGRlY29kaW5nIGZha2UgdG9rZW5zLiAgVGhpcyBvbmUncyBmb3IgaWRfdG9rZW4u"}/; - final cut = new AzureDeviceFlow(); - - final actualTokenPair = cut.buildTokenPair(input); - - assert "Q29uZ3JhdHVsYXRpb25zLCB5b3UgaGF2ZSBzdWNjZXNzZnVsbHkgZGVjb2RlZCBhIGZha2UgYWNjZXNzIHRva2VuLg==" == actualTokenPair.AccessToken.Value; - } - - @Ignore("Must be run manually after setting some system properties") - @Test public void endToEnd_manual() { - final tenantId = System.getProperty("tenantId"); - final clientId = System.getProperty("clientId"); - final resource = System.getProperty("resource"); - final String authorityUrl = "https://login.microsoftonline.com/" + tenantId; - final cut = new AzureDeviceFlow(); - cut.resource = resource; - final deviceEndpoint = URI.create(authorityUrl + "/oauth2/devicecode"); - final tokenEndpoint = URI.create(authorityUrl + "/oauth2/token"); - - final deviceFlowResponse = cut.requestAuthorization(deviceEndpoint, clientId, null); - System.err.println("------------------------------------"); - System.err.println("OAuth 2.0 Device Flow authentication"); - System.err.println("------------------------------------"); - System.err.println("To complete the authentication process, please open a web browser and visit the following URI:"); - System.err.println(deviceFlowResponse.getVerificationUri()); - System.err.println("When prompted, enter the following code:"); - System.err.println(deviceFlowResponse.getUserCode()); - System.err.println("Once authenticated and authorized, execution will continue."); - - final TokenPair actualTokens = cut.requestToken(tokenEndpoint, clientId, deviceFlowResponse); - - assert TokenType.Access == actualTokens.AccessToken.Type - } - -} diff --git a/core/src/test/groovy/com/microsoft/alm/auth/oauth/DeviceFlowImplTest.groovy b/core/src/test/groovy/com/microsoft/alm/auth/oauth/DeviceFlowImplTest.groovy deleted file mode 100644 index 39591f73..00000000 --- a/core/src/test/groovy/com/microsoft/alm/auth/oauth/DeviceFlowImplTest.groovy +++ /dev/null @@ -1,470 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth - -import com.github.tomakehurst.wiremock.http.Request -import com.github.tomakehurst.wiremock.http.RequestListener -import com.github.tomakehurst.wiremock.http.Response -import com.github.tomakehurst.wiremock.junit.WireMockRule -import com.github.tomakehurst.wiremock.stubbing.Scenario -import com.microsoft.alm.oauth2.useragent.AuthorizationException -import com.microsoft.alm.secret.TokenType -import groovy.transform.CompileStatic -import org.junit.After -import org.junit.Assert -import org.junit.Before -import org.junit.Rule -import org.junit.Test -import org.littleshoot.proxy.HttpProxyServer -import org.littleshoot.proxy.impl.DefaultHttpProxyServer - -import static com.github.tomakehurst.wiremock.client.WireMock.*; - -/** - * A class to test {@see DeviceFlowImpl}. - */ -@CompileStatic -public class DeviceFlowImplTest { - - private static final String PROTOCOL = "http"; - private static final String CLIENT_ID = "contoso"; - private static final String DEVICE_CODE = "9297fb18-46d0-4846-97ca-ab8dd3b55729"; - private static final String USER_CODE = "A1B2B4C1C5D1D3E3E5"; - private static final String ACCESS_TOKEN = "d15281b1-03f1-4581-90d3-4527d9cf4147"; - private static final URI VERIFICATION_URI = new URI("http://verification.example.com"); - private static final int EXPIRY_SECONDS = 600; - private static final int ATTEMPT_INTERVAL = 1; - private static final String DEVICE_ENDPOINT_PATH = "/device"; - private static final String TOKEN_ENDPOINT_PATH = "/token"; - private static final DeviceFlowResponse DEFAULT_DEVICE_FLOW_RESPONSE = new DeviceFlowResponse(DEVICE_CODE, USER_CODE, VERIFICATION_URI, EXPIRY_SECONDS, ATTEMPT_INTERVAL); - private static final String SCENARIO = "Default stateful scenario"; - private static final String ALL_INTERFACES_ADDRESS = "0.0.0.0"; - private static final InetSocketAddress PROXY_LISTEN_ADDRESS = new InetSocketAddress(ALL_INTERFACES_ADDRESS, 0); - - - private final String host; - private String scenarioStateName; - private int scenarioNextStateNumber; - private int deviceEndpointExpectedHits; - private int tokenEndpointExpectedErrorHits; - private int tokenEndpointExpectedSuccessHits; - - @Rule public WireMockRule wireMockRule = new WireMockRule(0); - - @Before public void initializeExpectedHits() { - scenarioStateName = Scenario.STARTED; - scenarioNextStateNumber = 0; - deviceEndpointExpectedHits = 0; - tokenEndpointExpectedErrorHits = 0; - tokenEndpointExpectedSuccessHits = 0; - } - - @After public void verifyExpectedHits() { - if (deviceEndpointExpectedHits > 0) { - verify(deviceEndpointExpectedHits, postRequestedFor(urlEqualTo(DEVICE_ENDPOINT_PATH))); - } - if (tokenEndpointExpectedErrorHits + tokenEndpointExpectedSuccessHits > 0) { - verify(tokenEndpointExpectedErrorHits + tokenEndpointExpectedSuccessHits, postRequestedFor(urlEqualTo(TOKEN_ENDPOINT_PATH))); - } - } - - public DeviceFlowImplTest() { - final def localHostAddress = InetAddress.localHost; - host = localHostAddress.hostName; - } - - private void stubDeviceEndpoint(final int interval = ATTEMPT_INTERVAL, final int expiresIn = -1, final String requestBodySuffix = "", String responseBodyPrefix = "") { - final def deviceRequestBody = "response_type=device_code&client_id=${CLIENT_ID}" + requestBodySuffix; - if (interval > 0) { - responseBodyPrefix += /"interval":${interval}, -/ - } - if (expiresIn > 0) { - responseBodyPrefix += /"expires_in":${expiresIn}, -/ - } - final def deviceResponseBody = """\ -{ - ${responseBodyPrefix} - "device_code":"${DEVICE_CODE}", - "user_code":"${USER_CODE}", - "verification_uri":"${VERIFICATION_URI}" -} -"""; - - final def nextStateName = Integer.toString(scenarioNextStateNumber, 10); - stubFor( - post( - urlEqualTo(DEVICE_ENDPOINT_PATH) - ) - .inScenario(SCENARIO) - .whenScenarioStateIs(scenarioStateName) - .withRequestBody( - equalTo(deviceRequestBody) - ) - .willReturn( - aResponse() - .withStatus(200) - .withHeader("Content-Type", "application/json") - .withHeader("Cache-Control", "no-store") - .withBody(deviceResponseBody) - ) - .willSetStateTo(nextStateName) - ); - deviceEndpointExpectedHits++; - scenarioStateName = nextStateName; - scenarioNextStateNumber++; - } - - private void stubTokenEndpointSuccess(final String requestBodySuffix = "", String responseBodyPrefix = "") { - final def tokenRequestBody = "grant_type=device_code&code=${DEVICE_CODE}&client_id=${CLIENT_ID}" + requestBodySuffix; - final def tokenResponseBody = """\ -{ - ${responseBodyPrefix} - "access_token":"${ACCESS_TOKEN}", - "token_type":"bearer", - "expires_in":3600 -} -"""; - - final def nextStateName = Integer.toString(scenarioNextStateNumber, 10); - stubFor( - post( - urlEqualTo(TOKEN_ENDPOINT_PATH) - ) - .inScenario(SCENARIO) - .whenScenarioStateIs(scenarioStateName) - .withRequestBody( - equalTo(tokenRequestBody) - ) - .willReturn( - aResponse() - .withStatus(200) - .withHeader("Content-Type", "application/json;charset=UTF-8") - .withHeader("Cache-Control", "no-store") - .withHeader("Pragma", "no-cache") - .withBody(tokenResponseBody) - ) - .willSetStateTo(nextStateName) - ); - tokenEndpointExpectedSuccessHits++; - scenarioStateName = nextStateName; - scenarioNextStateNumber++; - } - - private void stubTokenEndpointError(final String requestBody, final String errorCode, final String errorDescription = null, final URI errorUri = null) { - - final def tokenRequestBody = requestBody; - - def responseBodyPrefix = "" - if (errorDescription) { - responseBodyPrefix += /"error_description": "${errorDescription}", -/ - } - if (errorUri != null) { - responseBodyPrefix += /"error_uri": "${errorUri}", -/ - } - - final def tokenResponseBody = """\ -{ - ${responseBodyPrefix} - "error":"${errorCode}" -} -"""; - - final def nextStateName = Integer.toString(scenarioNextStateNumber, 10); - stubFor( - post( - urlEqualTo(TOKEN_ENDPOINT_PATH) - ) - .inScenario(SCENARIO) - .whenScenarioStateIs(scenarioStateName) - .withRequestBody( - equalTo(tokenRequestBody) - ) - .willReturn( - aResponse() - .withStatus(400) - .withHeader("Content-Type", "application/json;charset=UTF-8") - .withHeader("Cache-Control", "no-store") - .withHeader("Pragma", "no-cache") - .withBody(tokenResponseBody) - ) - .willSetStateTo(nextStateName) - ); - tokenEndpointExpectedErrorHits++; - scenarioStateName = nextStateName; - scenarioNextStateNumber++; - } - - @Test public void requestAuthorization_withScope() { - final def port = wireMockRule.port(); - final def deviceEndpoint = new URI(PROTOCOL, null, host, port, DEVICE_ENDPOINT_PATH, null, null); - stubDeviceEndpoint(ATTEMPT_INTERVAL, -1, "&scope=access_all_the_things") - final def cut = new DeviceFlowImpl(); - - final actualResponse = cut.requestAuthorization(deviceEndpoint, CLIENT_ID, "access_all_the_things") - - assert DEVICE_CODE == actualResponse.deviceCode; - assert USER_CODE == actualResponse.userCode; - assert VERIFICATION_URI == actualResponse.verificationUri; - assert EXPIRY_SECONDS == actualResponse.expiresIn; - assert ATTEMPT_INTERVAL == actualResponse.interval; - } - - @Test public void requestAuthorization_serverError() { - final def port = wireMockRule.port(); - final def deviceEndpoint = new URI(PROTOCOL, null, host, port, DEVICE_ENDPOINT_PATH, null, null); - stubFor(post(urlEqualTo(DEVICE_ENDPOINT_PATH)) - .willReturn(aResponse() - .withStatus(500) - .withBody("Internal server error!"))); - final def cut = new DeviceFlowImpl(); - - try { - cut.requestAuthorization(deviceEndpoint, CLIENT_ID, null) - } - catch (final Error e) { - final def actual = e.message.trim() - assert "java.io.IOException: HTTP request failed with code 500: Internal server error!" == actual; - return; - } - Assert.fail("An Error should have been thrown"); - } - - @Test public void requestToken_serverError() { - final def port = wireMockRule.port(); - final def tokenEndpoint = new URI(PROTOCOL, null, host, port, TOKEN_ENDPOINT_PATH, null, null); - stubFor(post(urlEqualTo(TOKEN_ENDPOINT_PATH)) - .willReturn(aResponse() - .withStatus(500) - .withBody("Internal server error!"))); - final def cut = new DeviceFlowImpl(); - - try { - cut.requestToken(tokenEndpoint, CLIENT_ID, DEFAULT_DEVICE_FLOW_RESPONSE) - } - catch (final Error e) { - final def actual = e.message.trim() - assert "Token endpoint returned HTTP 500:\nInternal server error!" == actual; - return; - } - Assert.fail("An Error should have been thrown"); - } - - @Test public void requestToken_waitsBetweenRequests() { - final def port = wireMockRule.port(); - final def tokenEndpoint = new URI(PROTOCOL, null, host, port, TOKEN_ENDPOINT_PATH, null, null); - final intervalMilliseconds = ATTEMPT_INTERVAL * 1000; - final listener = new RequestListener() { - private Calendar lastRequestTime = null; - @Override void requestReceived(final Request request, final Response response) { - final currentRequestTime = Calendar.instance; - if (lastRequestTime != null) { - final currentRequestMilliseconds = currentRequestTime.timeInMillis; - final lastRequestMilliseconds = lastRequestTime.timeInMillis; - assert currentRequestMilliseconds - lastRequestMilliseconds >= intervalMilliseconds; - } - lastRequestTime = currentRequestTime; - } - }; - wireMockRule.addMockServiceRequestListener(listener); - stubTokenEndpointError("grant_type=device_code&code=${DEVICE_CODE}&client_id=${CLIENT_ID}", "authorization_pending"); - stubTokenEndpointSuccess(); - final def cut = new DeviceFlowImpl(); - - final def actualTokenPair = cut.requestToken(tokenEndpoint, CLIENT_ID, DEFAULT_DEVICE_FLOW_RESPONSE); - - final def actualAccessToken = actualTokenPair.AccessToken; - assert TokenType.Access == actualAccessToken.Type; - assert ACCESS_TOKEN == actualAccessToken.Value; - } - - @Test public void requestToken_givesUpWhenCodeExpires() { - final def port = wireMockRule.port(); - final def tokenEndpoint = new URI(PROTOCOL, null, host, port, TOKEN_ENDPOINT_PATH, null, null); - stubTokenEndpointError("grant_type=device_code&code=${DEVICE_CODE}&client_id=${CLIENT_ID}", "authorization_pending"); - stubTokenEndpointError("grant_type=device_code&code=${DEVICE_CODE}&client_id=${CLIENT_ID}", "authorization_pending"); - final def cut = new DeviceFlowImpl(); - - final def response = new DeviceFlowResponse(DEVICE_CODE, USER_CODE, VERIFICATION_URI, 2, 1); - - try { - cut.requestToken(tokenEndpoint, CLIENT_ID, response) - } - catch (final AuthorizationException e) { - assert "code_expired" == e.code; - return; - } - Assert.fail("An AuthorizationException should have been thrown"); - } - - @Test public void requestToken_backsOff() { - final testStartTime = Calendar.instance; - final def port = wireMockRule.port(); - final def tokenEndpoint = new URI(PROTOCOL, null, host, port, TOKEN_ENDPOINT_PATH, null, null); - stubTokenEndpointError("grant_type=device_code&code=${DEVICE_CODE}&client_id=${CLIENT_ID}", "authorization_pending"); - stubTokenEndpointError("grant_type=device_code&code=${DEVICE_CODE}&client_id=${CLIENT_ID}", "slow_down"); - stubTokenEndpointError("grant_type=device_code&code=${DEVICE_CODE}&client_id=${CLIENT_ID}", "slow_down"); - stubTokenEndpointSuccess(); - final def cut = new DeviceFlowImpl(); - - final def actualTokenPair = cut.requestToken(tokenEndpoint, CLIENT_ID, DEFAULT_DEVICE_FLOW_RESPONSE); - - final def actualAccessToken = actualTokenPair.AccessToken; - assert TokenType.Access == actualAccessToken.Type; - assert ACCESS_TOKEN == actualAccessToken.Value; - final testEndTime = Calendar.instance; - assert testEndTime.timeInMillis - testStartTime.timeInMillis >= (1 + 2 + 4) * 1000 - } - - @Test public void endToEnd_authorizedRightAway() { - final def port = wireMockRule.port(); - final def deviceEndpoint = new URI(PROTOCOL, null, host, port, DEVICE_ENDPOINT_PATH, null, null); - final def tokenEndpoint = new URI(PROTOCOL, null, host, port, TOKEN_ENDPOINT_PATH, null, null); - stubDeviceEndpoint(); - stubTokenEndpointSuccess(); - final def cut = new DeviceFlowImpl(); - - final def actualResponse = cut.requestAuthorization(deviceEndpoint, CLIENT_ID, null); - - assert DEVICE_CODE == actualResponse.deviceCode; - assert USER_CODE == actualResponse.userCode; - assert VERIFICATION_URI == actualResponse.verificationUri; - assert EXPIRY_SECONDS == actualResponse.expiresIn; - assert ATTEMPT_INTERVAL == actualResponse.interval; - - final def actualTokenPair = cut.requestToken(tokenEndpoint, CLIENT_ID, actualResponse); - - final def actualAccessToken = actualTokenPair.AccessToken; - assert TokenType.Access == actualAccessToken.Type; - assert ACCESS_TOKEN == actualAccessToken.Value; - } - - @Test public void endToEnd_throughProxyServer() { - final def port = wireMockRule.port(); - final def oldProperties = System.properties; - final def adapter = new LoggingFiltersSourceAdapter(); - final def deviceEndpoint = new URI(PROTOCOL, null, host, port, DEVICE_ENDPOINT_PATH, null, null); - final def tokenEndpoint = new URI(PROTOCOL, null, host, port, TOKEN_ENDPOINT_PATH, null, null); - stubDeviceEndpoint(); - stubTokenEndpointSuccess(); - final HttpProxyServer proxyServer = - DefaultHttpProxyServer - .bootstrap() - .withAddress(PROXY_LISTEN_ADDRESS) - .withFiltersSource(adapter) - .start(); - final def cut = new DeviceFlowImpl(); - - try { - final def tempProperties = new Properties(oldProperties); - tempProperties.setProperty("http.proxyHost", host); - tempProperties.setProperty("http.nonProxyHosts", ""); - final InetSocketAddress proxyAddress = proxyServer.getListenAddress(); - tempProperties.setProperty("http.proxyPort", Integer.toString(proxyAddress.getPort(), 10)); - System.properties = tempProperties; - - final def actualResponse = cut.requestAuthorization(deviceEndpoint, CLIENT_ID, null); - - assert DEVICE_CODE == actualResponse.deviceCode; - assert USER_CODE == actualResponse.userCode; - assert VERIFICATION_URI == actualResponse.verificationUri; - assert EXPIRY_SECONDS == actualResponse.expiresIn; - assert ATTEMPT_INTERVAL == actualResponse.interval; - Assert.assertTrue(adapter.proxyWasUsed()); - - adapter.reset(); - - final def actualTokenPair = cut.requestToken(tokenEndpoint, CLIENT_ID, actualResponse); - - final def actualAccessToken = actualTokenPair.AccessToken; - assert TokenType.Access == actualAccessToken.Type; - assert ACCESS_TOKEN == actualAccessToken.Value; - Assert.assertTrue(adapter.proxyWasUsed()); - } - finally { - System.properties = oldProperties; - proxyServer.stop(); - } - } - - @Test public void endToEnd_deniedRightAway() { - final def port = wireMockRule.port(); - final def deviceEndpoint = new URI(PROTOCOL, null, host, port, DEVICE_ENDPOINT_PATH, null, null); - final def tokenEndpoint = new URI(PROTOCOL, null, host, port, TOKEN_ENDPOINT_PATH, null, null); - stubDeviceEndpoint(); - stubTokenEndpointError("grant_type=device_code&code=${DEVICE_CODE}&client_id=${CLIENT_ID}", "access_denied"); - final def cut = new DeviceFlowImpl(); - - final def actualResponse = cut.requestAuthorization(deviceEndpoint, CLIENT_ID, null); - - assert DEVICE_CODE == actualResponse.deviceCode; - assert USER_CODE == actualResponse.userCode; - assert VERIFICATION_URI == actualResponse.verificationUri; - assert EXPIRY_SECONDS == actualResponse.expiresIn; - assert ATTEMPT_INTERVAL == actualResponse.interval; - - try { - cut.requestToken(tokenEndpoint, CLIENT_ID, actualResponse) - } - catch (final AuthorizationException e) { - assert "access_denied" == e.code; - return; - } - Assert.fail("An AuthorizationException should have been thrown"); - } - - @Test public void endToEnd_authorizedAfterOnePending() { - final def port = wireMockRule.port(); - final def deviceEndpoint = new URI(PROTOCOL, null, host, port, DEVICE_ENDPOINT_PATH, null, null); - final def tokenEndpoint = new URI(PROTOCOL, null, host, port, TOKEN_ENDPOINT_PATH, null, null); - stubDeviceEndpoint(); - stubTokenEndpointError("grant_type=device_code&code=${DEVICE_CODE}&client_id=${CLIENT_ID}", "authorization_pending"); - stubTokenEndpointSuccess(); - final def cut = new DeviceFlowImpl(); - - final def actualResponse = cut.requestAuthorization(deviceEndpoint, CLIENT_ID, null); - - assert DEVICE_CODE == actualResponse.deviceCode; - assert USER_CODE == actualResponse.userCode; - assert VERIFICATION_URI == actualResponse.verificationUri; - assert EXPIRY_SECONDS == actualResponse.expiresIn; - assert ATTEMPT_INTERVAL == actualResponse.interval; - - final def actualTokenPair = cut.requestToken(tokenEndpoint, CLIENT_ID, actualResponse); - - final def actualAccessToken = actualTokenPair.AccessToken; - assert TokenType.Access == actualAccessToken.Type; - assert ACCESS_TOKEN == actualAccessToken.Value; - } - - @Test public void endToEnd_deniedAfterOnePending() { - final def port = wireMockRule.port(); - final def deviceEndpoint = new URI(PROTOCOL, null, host, port, DEVICE_ENDPOINT_PATH, null, null); - final def tokenEndpoint = new URI(PROTOCOL, null, host, port, TOKEN_ENDPOINT_PATH, null, null); - stubDeviceEndpoint(); - stubTokenEndpointError("grant_type=device_code&code=${DEVICE_CODE}&client_id=${CLIENT_ID}", "authorization_pending"); - stubTokenEndpointError("grant_type=device_code&code=${DEVICE_CODE}&client_id=${CLIENT_ID}", "access_denied"); - final def cut = new DeviceFlowImpl(); - - final def actualResponse = cut.requestAuthorization(deviceEndpoint, CLIENT_ID, null); - - assert DEVICE_CODE == actualResponse.deviceCode; - assert USER_CODE == actualResponse.userCode; - assert VERIFICATION_URI == actualResponse.verificationUri; - assert EXPIRY_SECONDS == actualResponse.expiresIn; - assert ATTEMPT_INTERVAL == actualResponse.interval; - - try { - cut.requestToken(tokenEndpoint, CLIENT_ID, actualResponse) - } - catch (final AuthorizationException e) { - assert "access_denied" == e.code; - return; - } - Assert.fail("An AuthorizationException should have been thrown"); - } -} diff --git a/core/src/test/groovy/com/microsoft/alm/auth/oauth/DeviceFlowResponseTest.groovy b/core/src/test/groovy/com/microsoft/alm/auth/oauth/DeviceFlowResponseTest.groovy deleted file mode 100644 index 5bb3465b..00000000 --- a/core/src/test/groovy/com/microsoft/alm/auth/oauth/DeviceFlowResponseTest.groovy +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth - -import groovy.transform.CompileStatic -import org.junit.Test - -/** - * A class to test {@see DeviceFlowResponse}. - */ -@CompileStatic -public class DeviceFlowResponseTest { - - @Test public void fromJson_deviceFlowDraft01() { - final now = Calendar.instance - final def input = """\ -{ - "device_code":"74tq5miHKB", - "user_code":"94248", - "verification_uri":"http://www.example.com/device", - "interval":5 -} -""" - - final actual = DeviceFlowResponse.fromJson(input) - - assert "74tq5miHKB" == actual.deviceCode - assert "94248" == actual.userCode - assert URI.create("http://www.example.com/device") == actual.verificationUri - assert 5 == actual.interval - assert 600 == actual.expiresIn - assert actual.expiresAt.timeInMillis - now.timeInMillis >= 600 - } - -} diff --git a/core/src/test/java/com/microsoft/alm/auth/basic/BasicAuthAuthenticatorTest.java b/core/src/test/java/com/microsoft/alm/auth/basic/BasicAuthAuthenticatorTest.java deleted file mode 100644 index 7915fafa..00000000 --- a/core/src/test/java/com/microsoft/alm/auth/basic/BasicAuthAuthenticatorTest.java +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.basic; - -import com.microsoft.alm.auth.PromptBehavior; -import com.microsoft.alm.secret.Credential; -import com.microsoft.alm.storage.SecretStore; -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mockito; - -import java.net.URI; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -public class BasicAuthAuthenticatorTest { - - private BasicAuthAuthenticator underTest; - - private SecretStore mockStore; - @Before - public void setUp() throws Exception { - mockStore = Mockito.mock(SecretStore.class); - - underTest = new BasicAuthAuthenticator(mockStore, new CredentialPrompt(){ - @Override - public Credential prompt(URI target) { - return new Credential("user", "pass"); - } - }); - } - - @Test - public void noCredentialStoredShouldPrompt() { - URI uri = URI.create("http://test.com"); - String key = underTest.getKey(uri); - Credential credential = underTest.getCredential(uri); - - // should call get once and don't get anything - verify(mockStore).get(key); - - // and invoke the credential prompt which returns back users:pass - assertEquals("user", credential.Username); - assertEquals("pass", credential.Password); - - // and then store this value - verify(mockStore).add(key, credential); - } - - @Test - public void withCredentialStoredRetrieveStoredValue() { - URI uri = URI.create("http://test.com"); - String key = underTest.getKey(uri); - - when(mockStore.get(key)).thenReturn(new Credential("storedUser", "storedPass")); - - Credential credential = underTest.getCredential(uri); - - // should return stored value instead of default prompted value - assertEquals("storedUser", credential.Username); - assertEquals("storedPass", credential.Password); - - verify(mockStore, never()).add(anyString(), any(Credential.class)); - } - - @Test - public void promptBehaviorNeverShouldNotPrompt() { - URI uri = URI.create("http://test.com"); - String key = underTest.getKey(uri); - - Credential credential = underTest.getCredential(uri, PromptBehavior.NEVER); - // Store miss, and never prompt should return null - assertNull(credential); - } - - @Test - public void typeIsBasic() { - assertEquals("BasicAuth", underTest.getAuthType()); - } - - @Test - public void credentialIsSupported() { - assertTrue(underTest.isCredentialSupported()); - - assertFalse(underTest.isOAuth2TokenSupported()); - assertFalse(underTest.isPersonalAccessTokenSupported()); - } -} \ No newline at end of file diff --git a/core/src/test/java/com/microsoft/alm/auth/oauth/AzureAuthorityTest.java b/core/src/test/java/com/microsoft/alm/auth/oauth/AzureAuthorityTest.java deleted file mode 100644 index b7c9298d..00000000 --- a/core/src/test/java/com/microsoft/alm/auth/oauth/AzureAuthorityTest.java +++ /dev/null @@ -1,271 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth; - -import com.microsoft.alm.auth.PromptBehavior; -import com.microsoft.alm.helpers.Action; -import com.microsoft.alm.helpers.StringContent; -import com.microsoft.alm.oauth2.useragent.AuthorizationException; -import com.microsoft.alm.secret.TokenPair; -import org.junit.Assert; -import org.junit.Test; - -import java.net.URI; -import java.util.UUID; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * A class to test {@link AzureAuthority}. - */ -public class AzureAuthorityTest { - - static final String TEST_RESOURCE = "TEST_RESOURCE"; - static final String TEST_CLIENT_ID = "d30feefe-9ee4-4b00-ac77-08dbd1199811"; - static final String TEST_DEVICE_CODE = "03d5f4b1-c8ab-4ce2-85c0-158d2075ff5f"; - static final String TEST_USER_CODE = "DEADBEEF"; - static final int TEST_EXPIRATION = 600; - static final int TEST_INTERVAL = 5; - static final String TEST_ACCESS_TOKEN = "bacf8b5f-63f2-4998-9170-d32cf7db4a78"; - static final String TEST_REFRESH_TOKEN = "c2be2d76-1e9e-487c-9684-78823747391c"; - static final URI TEST_REDIRECT_URI = URI.create("https://redirect.test"); - - @Test - public void deviceFlow_success() throws Exception { - final String authorityHostUrl = "https://authorization.example.com/common/"; - final URI verificationUri = URI.create("https://authorization.example.com/oauth/device"); - final AtomicInteger requestAuthorizationCalls = new AtomicInteger(0); - final AtomicInteger requestTokenCalls = new AtomicInteger(0); - final AtomicInteger callbackCalls = new AtomicInteger(0); - final AzureDeviceFlowResponse azureDeviceFlowResponse = new AzureDeviceFlowResponse(TEST_DEVICE_CODE, TEST_USER_CODE, verificationUri, TEST_EXPIRATION, TEST_INTERVAL, "message"); - final AzureDeviceFlow testDeviceFlow = new AzureDeviceFlow() { - @Override - public DeviceFlowResponse requestAuthorization(final URI deviceEndpoint, final String clientId, final String scope) { - requestAuthorizationCalls.addAndGet(1); - Assert.assertEquals(TEST_CLIENT_ID, clientId); - return azureDeviceFlowResponse; - } - - @Override - public TokenPair requestToken(final URI tokenEndpoint, final String clientId, final DeviceFlowResponse deviceFlowResponse) throws AuthorizationException { - requestTokenCalls.addAndGet(1); - Assert.assertEquals(TEST_CLIENT_ID, clientId); - Assert.assertEquals(azureDeviceFlowResponse, deviceFlowResponse); - deviceFlowResponse.setTokenAcquired(); - return new TokenPair(TEST_ACCESS_TOKEN, TEST_REFRESH_TOKEN); - } - }; - final Action callback = new Action() { - @Override - public void call(final DeviceFlowResponse deviceFlowResponse) { - callbackCalls.addAndGet(1); - Assert.assertEquals(azureDeviceFlowResponse, deviceFlowResponse); - } - }; - final AzureAuthority cut = new AzureAuthority(authorityHostUrl, null, testDeviceFlow); - - final TokenPair actualTokenPair = cut.acquireToken(TEST_CLIENT_ID, TEST_RESOURCE, TEST_REDIRECT_URI, callback); - - Assert.assertEquals(TEST_ACCESS_TOKEN, actualTokenPair.AccessToken.Value); - Assert.assertEquals(TEST_REFRESH_TOKEN, actualTokenPair.RefreshToken.Value); - Assert.assertEquals(TEST_RESOURCE, testDeviceFlow.getResource()); - Assert.assertEquals(1, requestAuthorizationCalls.get()); - Assert.assertEquals(1, requestTokenCalls.get()); - Assert.assertEquals(1, callbackCalls.get()); - Assert.assertTrue(azureDeviceFlowResponse.isTokenAcquired()); - } - - @Test - public void deviceFlow_failure() throws Exception { - final String authorityHostUrl = "https://authorization.example.com/common/"; - final URI verificationUri = URI.create("https://authorization.example.com/oauth/device"); - final AtomicInteger requestAuthorizationCalls = new AtomicInteger(0); - final AtomicInteger requestTokenCalls = new AtomicInteger(0); - final AtomicInteger callbackCalls = new AtomicInteger(0); - final AzureDeviceFlowResponse azureDeviceFlowResponse = new AzureDeviceFlowResponse(TEST_DEVICE_CODE, TEST_USER_CODE, verificationUri, TEST_EXPIRATION, TEST_INTERVAL, "message"); - final AzureDeviceFlow testDeviceFlow = new AzureDeviceFlow() { - @Override - public DeviceFlowResponse requestAuthorization(final URI deviceEndpoint, final String clientId, final String scope) { - requestAuthorizationCalls.addAndGet(1); - Assert.assertEquals(TEST_CLIENT_ID, clientId); - return azureDeviceFlowResponse; - } - - @Override - public TokenPair requestToken(final URI tokenEndpoint, final String clientId, final DeviceFlowResponse deviceFlowResponse) throws AuthorizationException { - requestTokenCalls.addAndGet(1); - throw new AuthorizationException("access_denied"); - } - }; - final Action callback = new Action() { - @Override - public void call(final DeviceFlowResponse deviceFlowResponse) { - callbackCalls.addAndGet(1); - Assert.assertEquals(azureDeviceFlowResponse, deviceFlowResponse); - } - }; - final AzureAuthority cut = new AzureAuthority(authorityHostUrl, null, testDeviceFlow); - - try { - cut.acquireToken(TEST_CLIENT_ID, TEST_RESOURCE, TEST_REDIRECT_URI, callback); - - Assert.fail("An exception should have been thrown before this line."); - } catch (final AuthorizationException e) { - Assert.assertEquals("access_denied", e.getCode()); - } - - Assert.assertEquals(TEST_RESOURCE, testDeviceFlow.getResource()); - Assert.assertEquals(1, requestAuthorizationCalls.get()); - Assert.assertEquals(1, requestTokenCalls.get()); - Assert.assertEquals(1, callbackCalls.get()); - } - - @Test - public void deviceFlow_cancelled() throws Exception { - final String authorityHostUrl = "https://authorization.example.com/common/"; - final URI verificationUri = URI.create("https://authorization.example.com/oauth/device"); - final AtomicInteger requestAuthorizationCalls = new AtomicInteger(0); - final AtomicInteger requestTokenCalls = new AtomicInteger(0); - final AtomicInteger callbackCalls = new AtomicInteger(0); - final AzureDeviceFlowResponse azureDeviceFlowResponse = new AzureDeviceFlowResponse(TEST_DEVICE_CODE, TEST_USER_CODE, verificationUri, TEST_EXPIRATION, TEST_INTERVAL, "message"); - final AzureDeviceFlow testDeviceFlow = new AzureDeviceFlow() { - @Override - public DeviceFlowResponse requestAuthorization(final URI deviceEndpoint, final String clientId, final String scope) { - requestAuthorizationCalls.addAndGet(1); - Assert.assertEquals(TEST_CLIENT_ID, clientId); - return azureDeviceFlowResponse; - } - }; - final Action callback = new Action() { - @Override - public void call(final DeviceFlowResponse deviceFlowResponse) { - callbackCalls.addAndGet(1); - Assert.assertEquals(azureDeviceFlowResponse, deviceFlowResponse); - deviceFlowResponse.requestCancel(); - } - }; - final AzureAuthority cut = new AzureAuthority(authorityHostUrl, null, testDeviceFlow); - - try { - cut.acquireToken(TEST_CLIENT_ID, TEST_RESOURCE, TEST_REDIRECT_URI, callback); - - Assert.fail("An exception should have been thrown before this line."); - } catch (final AuthorizationException e) { - Assert.assertEquals("request_cancelled", e.getCode()); - } - - Assert.assertEquals(TEST_RESOURCE, testDeviceFlow.getResource()); - Assert.assertEquals(1, requestAuthorizationCalls.get()); - Assert.assertEquals(0, requestTokenCalls.get()); - Assert.assertEquals(1, callbackCalls.get()); - } - - @Test - public void createAuthorizationEndpointUri_minimal() throws Exception { - final URI redirectUri = URI.create("https://example.com"); - final URI actual = AzureAuthority.createAuthorizationEndpointUri( - "https://login.microsoftonline.com/common", - "a8860e8f-ca7d-4efe-b80d-4affab13d4ba", "f7e11bcd-b50b-4869-ad88-8bdd6cbc8473", - redirectUri, - UserIdentifier.ANY_USER, - null, - PromptBehavior.AUTO, - null); - - Assert.assertEquals("https://login.microsoftonline.com/common/oauth2/authorize?resource=a8860e8f-ca7d-4efe-b80d-4affab13d4ba&client_id=f7e11bcd-b50b-4869-ad88-8bdd6cbc8473&response_type=code&redirect_uri=https%3A%2F%2Fexample.com", actual.toString()); - } - - @Test - public void createAuthorizationEndpointUri_typical() throws Exception { - final URI redirectUri = URI.create("https://example.com"); - final String state = "519a4fa6-c18f-4230-8290-6c57407656c9"; - final URI actual = AzureAuthority.createAuthorizationEndpointUri( - "https://login.microsoftonline.com/common", - "a8860e8f-ca7d-4efe-b80d-4affab13d4ba", "f7e11bcd-b50b-4869-ad88-8bdd6cbc8473", - redirectUri, - UserIdentifier.ANY_USER, - state, - PromptBehavior.ALWAYS, - null); - - Assert.assertEquals("https://login.microsoftonline.com/common/oauth2/authorize?resource=a8860e8f-ca7d-4efe-b80d-4affab13d4ba&client_id=f7e11bcd-b50b-4869-ad88-8bdd6cbc8473&response_type=code&redirect_uri=https%3A%2F%2Fexample.com&state=519a4fa6-c18f-4230-8290-6c57407656c9&prompt=login", actual.toString()); - } - - @Test - public void createAuthorizationEndpointUri_extraState() throws Exception { - final URI redirectUri = URI.create("https://example.com"); - final String state = "519a4fa6-c18f-4230-8290-6c57407656c9"; - final URI actual = AzureAuthority.createAuthorizationEndpointUri( - "https://login.microsoftonline.com/common", - "a8860e8f-ca7d-4efe-b80d-4affab13d4ba", "f7e11bcd-b50b-4869-ad88-8bdd6cbc8473", - redirectUri, - UserIdentifier.ANY_USER, - state, - PromptBehavior.ALWAYS, - "state=bliss"); - - Assert.assertEquals("https://login.microsoftonline.com/common/oauth2/authorize?resource=a8860e8f-ca7d-4efe-b80d-4affab13d4ba&client_id=f7e11bcd-b50b-4869-ad88-8bdd6cbc8473&response_type=code&redirect_uri=https%3A%2F%2Fexample.com&state=519a4fa6-c18f-4230-8290-6c57407656c9&prompt=login&state=bliss", actual.toString()); - } - - @Test - public void createTokenEndpointUri_typical() throws Exception { - final URI actual = AzureAuthority.createTokenEndpointUri("https://login.example.com/common"); - - Assert.assertEquals("https://login.example.com/common/oauth2/token", actual.toString()); - } - - @Test - public void createTokenRequest_typical() throws Exception { - final String resource = "a8860e8f-ca7d-4efe-b80d-4affab13d4ba"; - final String clientId = "f7e11bcd-b50b-4869-ad88-8bdd6cbc8473"; - // authorization codes can be pretty long - final String authorizationCode = - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; - final URI redirectUri = URI.create("https://example.com"); - - final StringContent actual = AzureAuthority.createTokenRequest(resource, clientId, authorizationCode, redirectUri, null); - - Assert.assertEquals( - "resource=a8860e8f-ca7d-4efe-b80d-4affab13d4ba" + - "&client_id=f7e11bcd-b50b-4869-ad88-8bdd6cbc8473" + - "&grant_type=authorization_code" + - "&code=" + authorizationCode + - "&redirect_uri=https%3A%2F%2Fexample.com", actual.getContent()); - } - - @Test - public void createTokenRequest_withCorrelationId() throws Exception { - final String resource = "a8860e8f-ca7d-4efe-b80d-4affab13d4ba"; - final String clientId = "f7e11bcd-b50b-4869-ad88-8bdd6cbc8473"; - final UUID correlationId = UUID.fromString("519a4fa6-c18f-4230-8290-6c57407656c9"); - // authorization codes can be pretty long - final String authorizationCode = - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA" + - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"; - final URI redirectUri = URI.create("https://example.com"); - - final StringContent actual = AzureAuthority.createTokenRequest(resource, clientId, authorizationCode, redirectUri, correlationId); - - Assert.assertEquals( - "resource=a8860e8f-ca7d-4efe-b80d-4affab13d4ba" + - "&client_id=f7e11bcd-b50b-4869-ad88-8bdd6cbc8473" + - "&grant_type=authorization_code" + - "&code=" + authorizationCode + - "&redirect_uri=https%3A%2F%2Fexample.com" + - "&client-request-id=519a4fa6-c18f-4230-8290-6c57407656c9" + - "&return-client-request-id=true", actual.getContent()); - } -} diff --git a/core/src/test/java/com/microsoft/alm/auth/oauth/LoggingFiltersSourceAdapter.java b/core/src/test/java/com/microsoft/alm/auth/oauth/LoggingFiltersSourceAdapter.java deleted file mode 100644 index 08582476..00000000 --- a/core/src/test/java/com/microsoft/alm/auth/oauth/LoggingFiltersSourceAdapter.java +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth; - -import io.netty.channel.ChannelHandlerContext; -import io.netty.handler.codec.http.FullHttpRequest; -import io.netty.handler.codec.http.FullHttpResponse; -import io.netty.handler.codec.http.HttpObject; -import io.netty.handler.codec.http.HttpRequest; -import io.netty.handler.codec.http.HttpResponse; -import org.littleshoot.proxy.HttpFilters; -import org.littleshoot.proxy.HttpFiltersAdapter; -import org.littleshoot.proxy.HttpFiltersSourceAdapter; - -import java.util.ArrayList; -import java.util.List; - -public class LoggingFiltersSourceAdapter extends HttpFiltersSourceAdapter { - - private final List requests = new ArrayList(); - private final List responses = new ArrayList(); - - /* - https://github.com/adamfisk/LittleProxy - """ - To enable aggregator and inflater you have to return a value greater than 0 in your - `HttpFiltersSource#get(Request/HttpResponse)BufferSizeInBytes()` methods. - This provides to you a `FullHttp(Request/HttpResponse)` - with the complete content in your filter uncompressed. - Otherwise you have to handle the chunks yourself. - """ - */ - - @Override public int getMaximumRequestBufferSizeInBytes() { - return 10 * 1024 * 1024; - } - - @Override public int getMaximumResponseBufferSizeInBytes() { - return 10 * 1024 * 1024; - } - - @Override public HttpFilters filterRequest(final HttpRequest originalRequest, final ChannelHandlerContext ctx) { - return new HttpFiltersAdapter(originalRequest, ctx) { - @Override public HttpResponse clientToProxyRequest(final HttpObject httpObject) { - requests.add((FullHttpRequest) httpObject); - return /* "[return] null to continue processing as usual" */ null; - } - - @Override public HttpObject serverToProxyResponse(final HttpObject httpObject) { - responses.add((FullHttpResponse) httpObject); - return /* [return] the unmodified HttpObject */ httpObject; - } - }; - } - - public boolean proxyWasUsed() { - return requests.size() > 0; - } - - public void reset() { - requests.clear(); - responses.clear(); - } -} diff --git a/core/src/test/java/com/microsoft/alm/auth/oauth/OAuth2AuthenticatorTest.java b/core/src/test/java/com/microsoft/alm/auth/oauth/OAuth2AuthenticatorTest.java deleted file mode 100644 index f103e0bf..00000000 --- a/core/src/test/java/com/microsoft/alm/auth/oauth/OAuth2AuthenticatorTest.java +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth; - -import com.microsoft.alm.auth.oauth.helper.AzureAuthorityProvider; -import com.microsoft.alm.helpers.Action; -import com.microsoft.alm.oauth2.useragent.AuthorizationException; -import com.microsoft.alm.secret.TokenPair; -import com.microsoft.alm.storage.SecretStore; -import org.junit.Before; -import org.junit.Test; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.UUID; -import java.util.concurrent.ExecutionException; - -import static junit.framework.TestCase.assertTrue; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.mockito.Matchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class OAuth2AuthenticatorTest { - - private OAuth2Authenticator underTest; - - private SecretStore mockStore; - - private AzureAuthority mockAzureAuthority; - - private AzureAuthorityProvider mockAzureAuthorityProvider; - - private OAuth2UseragentValidator mockOAuth2UseragentValidator; - - private Action testCallback; - - private final UUID clientId = UUID.randomUUID(); - - private final URI TEST_REDIRECT_URI = URI.create("https://redirect.test"); - private final String TEST_RESOURCE = "test_resource"; - - @Before - public void setUp() throws Exception { - mockStore = mock(SecretStore.class); - mockAzureAuthority = mock(AzureAuthority.class); - mockAzureAuthorityProvider = mock(AzureAuthorityProvider.class); - mockOAuth2UseragentValidator = mock(OAuth2UseragentValidator.class); - testCallback = new Action() { - @Override - public void call(final DeviceFlowResponse deviceFlowResponse) { - // do nothing on purpose - } - }; - - when(mockOAuth2UseragentValidator.isOnlyMissingRuntimeFromSwtProvider()).thenReturn(false); - when(mockAzureAuthorityProvider.getAzureAuthority(any(URI.class))).thenReturn(mockAzureAuthority); - - underTest = new OAuth2Authenticator(TEST_RESOURCE, - clientId.toString(), - TEST_REDIRECT_URI, - mockStore, - mockOAuth2UseragentValidator, - testCallback); - - underTest.setAzureAuthorityProvider(mockAzureAuthorityProvider); - } - - @Test - public void getTokenByAcquireToken_if_oauth2_useragent_available() - throws URISyntaxException, AuthorizationException { - when(mockOAuth2UseragentValidator.isOAuth2ProviderAvailable()).thenReturn(true); - when(mockAzureAuthority.acquireToken(clientId.toString(), TEST_RESOURCE, - TEST_REDIRECT_URI, underTest.POPUP_QUERY_PARAM)) - .thenReturn(new TokenPair("access", "refresh")); - - TokenPair token = underTest.getOAuth2TokenPair(); - - assertEquals("access", token.AccessToken.Value); - assertEquals("refresh", token.RefreshToken.Value); - - } - - @Test - public void getTokenByAcquireAuthenticationResult_if_neither_browser_is_available() - throws URISyntaxException, InterruptedException, ExecutionException, IOException, AuthorizationException { - when(mockOAuth2UseragentValidator.isOAuth2ProviderAvailable()).thenReturn(false); - when(mockAzureAuthority.acquireToken(clientId.toString(), TEST_RESOURCE, TEST_REDIRECT_URI, testCallback)) - .thenReturn(new TokenPair("access", "refresh")); - - final TokenPair token = underTest.getOAuth2TokenPair(); - - assertEquals("access", token.AccessToken.Value); - assertEquals("refresh", token.RefreshToken.Value); - } - - @Test - public void getTokenByRefreshToken_if_existingAccessTokenNotValid() - throws URISyntaxException, InterruptedException, ExecutionException, IOException, AuthorizationException { - when(mockOAuth2UseragentValidator.isOAuth2ProviderAvailable()).thenReturn(false); - when(mockAzureAuthority.acquireToken(clientId.toString(), TEST_RESOURCE, TEST_REDIRECT_URI, testCallback)) - .thenReturn(new TokenPair("access", "refresh")); - - final TokenPair token = underTest.getOAuth2TokenPair(); - - assertEquals("access", token.AccessToken.Value); - assertEquals("refresh", token.RefreshToken.Value); - } - - @Test - public void getTokenByAcquireAuthenticationResult_if_nothing_is_available() - throws URISyntaxException, InterruptedException, ExecutionException, IOException, AuthorizationException { - when(mockOAuth2UseragentValidator.isOAuth2ProviderAvailable()).thenReturn(false); - final OAuth2Authenticator underTest = new OAuth2Authenticator(TEST_RESOURCE, - clientId.toString(), - TEST_REDIRECT_URI, - mockStore, - mockOAuth2UseragentValidator, - null /* no callback specified */); - - final TokenPair token = underTest.getOAuth2TokenPair(); - - assertEquals(null, token); - } - - @Test - public void typeIsOAuth2() { - assertEquals("OAuth2", underTest.getAuthType()); - } - - @Test - public void oauth2IsSupported() { - assertTrue(underTest.isOAuth2TokenSupported()); - - assertFalse(underTest.isCredentialSupported()); - assertFalse(underTest.isPersonalAccessTokenSupported()); - } - -} \ No newline at end of file diff --git a/core/src/test/java/com/microsoft/alm/auth/oauth/helper/FileHashHelperTest.java b/core/src/test/java/com/microsoft/alm/auth/oauth/helper/FileHashHelperTest.java deleted file mode 100644 index 31d23307..00000000 --- a/core/src/test/java/com/microsoft/alm/auth/oauth/helper/FileHashHelperTest.java +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth.helper; - -import org.junit.Test; - -import java.io.BufferedWriter; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.PrintWriter; - -import static org.junit.Assert.assertTrue; - -public class FileHashHelperTest { - - @Test(expected = IOException.class) - public void calculateCRC32Hash_nonExistentFile() throws IOException { - final File nonExistent = File.createTempFile("FileHashHelperTestCRC32", "unittest"); - nonExistent.delete(); - - FileHashHelper.crc32Hash(nonExistent); - } - - @Test(expected = IOException.class) - public void calculateCRC32Hash_nullInputStream() throws IOException { - FileHashHelper.crc32Hash((InputStream) null); - } - - @Test - public void calculateCRCHash() throws IOException { - final File test = File.createTempFile("FileHashHelperTestCRC32", "unittest"); - final PrintWriter pw = new PrintWriter(new BufferedWriter(new FileWriter(test))); - pw.print("This is a unit test"); - pw.close(); - - long hash = FileHashHelper.crc32Hash(test); - assertTrue(hash != 0); - - // I got this hash from my mac. Make sure the same hash is calculated on different platforms - assertTrue(hash == 3677983296L); - - System.out.println(hash); - } -} \ No newline at end of file diff --git a/core/src/test/java/com/microsoft/alm/auth/oauth/helper/SwtJarLoaderTest.java b/core/src/test/java/com/microsoft/alm/auth/oauth/helper/SwtJarLoaderTest.java deleted file mode 100644 index 02c0acc9..00000000 --- a/core/src/test/java/com/microsoft/alm/auth/oauth/helper/SwtJarLoaderTest.java +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.oauth.helper; - -import org.junit.Test; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; - -public class SwtJarLoaderTest { - - @Test - public void testGetJarName() throws Exception { - /** - *this name should finally look like one of those: - * org.eclipse.swt.cocoa.macosx.x86-4.4.2.jar - * org.eclipse.swt.cocoa.macosx.x86_64-4.4.2.jar - * org.eclipse.swt.gtk.linux.x86-4.4.2.jar - * org.eclipse.swt.gtk.linux.x86_64-4.4.2.jar - * org.eclipse.swt.win32.win32.x86-4.4.2.jar - * org.eclipse.swt.win32.win32.x86_64-4.4.2.jar - */ - final String mac64bit = SwtJarLoader.getJarName(false, false, true, true); - isFilenameCorrect("org.eclipse.swt.cocoa.macosx.x86_64-4.4.2.jar", mac64bit); - - final String mac32bit = SwtJarLoader.getJarName(false, false, true, false); - isFilenameCorrect("org.eclipse.swt.cocoa.macosx.x86-4.4.2.jar", mac32bit); - - final String linux64bit = SwtJarLoader.getJarName(false, true, false, true); - isFilenameCorrect("org.eclipse.swt.gtk.linux.x86_64-4.4.2.jar", linux64bit); - - final String linux32bit = SwtJarLoader.getJarName(false, true, false, false); - isFilenameCorrect("org.eclipse.swt.gtk.linux.x86-4.4.2.jar", linux32bit); - - final String win64bit = SwtJarLoader.getJarName(true, false, false, true); - isFilenameCorrect("org.eclipse.swt.win32.win32.x86_64-4.4.2.jar", win64bit); - - final String win32bit = SwtJarLoader.getJarName(true, false, false, false); - isFilenameCorrect("org.eclipse.swt.win32.win32.x86-4.4.2.jar", win32bit); - } - - private void isFilenameCorrect(final String expected, final String actual) { - assertEquals(expected, actual); - assertNotNull(SwtJarLoader.CRC32_HASHES.get(actual)); - } - -} \ No newline at end of file diff --git a/core/src/test/java/com/microsoft/alm/auth/pat/VstsPatAuthenticatorTest.java b/core/src/test/java/com/microsoft/alm/auth/pat/VstsPatAuthenticatorTest.java deleted file mode 100644 index 528f4394..00000000 --- a/core/src/test/java/com/microsoft/alm/auth/pat/VstsPatAuthenticatorTest.java +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.auth.pat; - -import com.microsoft.alm.auth.PromptBehavior; -import com.microsoft.alm.auth.oauth.OAuth2Authenticator; -import com.microsoft.alm.secret.Token; -import com.microsoft.alm.secret.TokenPair; -import com.microsoft.alm.secret.TokenType; -import com.microsoft.alm.secret.VsoTokenScope; -import com.microsoft.alm.storage.SecretStore; -import org.junit.Before; -import org.junit.Test; - -import java.net.URI; - -import static junit.framework.Assert.assertEquals; -import static junit.framework.TestCase.assertTrue; -import static org.junit.Assert.assertFalse; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class VstsPatAuthenticatorTest { - - private VstsPatAuthenticator underTest; - - private VsoAzureAuthority mockVsoAzureAuthority; - - private OAuth2Authenticator mockVstsOauthAuthenticator; - - private SecretStore tokenStore; - - @Before - public void setUp() throws Exception { - tokenStore = mock(SecretStore.class); - mockVsoAzureAuthority = mock(VsoAzureAuthority.class); - mockVstsOauthAuthenticator = mock(OAuth2Authenticator.class); - - underTest = new VstsPatAuthenticator(mockVsoAzureAuthority, mockVstsOauthAuthenticator, tokenStore); - } - - @Test - public void testGetPersonalAccessToken() throws Exception { - URI uri = URI.create("https://testuri.visualstudio.com"); - TokenPair tokenPair = new TokenPair("access", "refresh"); - when(mockVstsOauthAuthenticator.getOAuth2TokenPair(uri, PromptBehavior.NEVER)).thenReturn(null); - when(mockVstsOauthAuthenticator.getOAuth2TokenPair(uri, PromptBehavior.AUTO)).thenReturn(tokenPair); - - when(mockVsoAzureAuthority.generatePersonalAccessToken(uri, tokenPair.AccessToken, VsoTokenScope.AllScopes, true, - false, "PAT")).thenReturn(new Token("token", TokenType.Personal)); - - Token token = underTest.getPersonalAccessToken(uri, VsoTokenScope.AllScopes, "PAT", PromptBehavior.AUTO); - - assertEquals("token", token.Value); - } - - @Test - public void testGetAuthType() throws Exception { - assertEquals("PersonalAccessToken", underTest.getAuthType()); - } - - @Test - public void patIsSupported() { - assertTrue(underTest.isPersonalAccessTokenSupported()); - - assertFalse(underTest.isOAuth2TokenSupported()); - assertFalse(underTest.isCredentialSupported()); - } -} \ No newline at end of file diff --git a/pom.xml b/pom.xml index 5adc0693..7ce8acc8 100644 --- a/pom.xml +++ b/pom.xml @@ -1,51 +1,57 @@ - + 4.0.0 - com.microsoft.alm - auth-lib-parent - 0.6.5 - pom + com.microsoft.a4o + credential-secure-storage + 1.0.0 + jar - Authentication Library for Visual Studio Team Services and Team Foundation Server - Parent POM for supporting multi-module Authentication library for VSTS and TFS - https://java.visualstudio.com/ + Secure storage for storing credentials or tokens. + Unified platform-independent interface to store secrets in credential managers provided by OS (Windows, macOS, Linux). - - common - storage - core - providers - sample - + + + microsoft + Microsoft + + MIT License - http://www.opensource.org/licenses/mit-license.php + http://opensource.org/licenses/MIT + repo - - - David Staheli - dastahel@microsoft.com - Microsoft - http://www.microsoft.com/ - - - Yang Cao - yacao@microsoft.com - Microsoft - http://www.microsoft.com/ - - - - scm:git:https://github.com/Microsoft/vsts-authentication-library-for-java.git - scm:git:https://github.com/Microsoft/vsts-authentication-library-for-java.git - https://github.com/Microsoft/vsts-authentication-library-for-java + scm:git:https://github.com/Microsoft/credential-secure-storage-for-java.git + scm:git:https://github.com/Microsoft/credential-secure-storage-for-java.git + https://github.com/Microsoft/credential-secure-storage-for-java HEAD @@ -63,12 +69,23 @@ Licensed under the MIT license. See License.txt in the project root. --> UTF-8 UTF-8 + 1.7.36 - 0.11.3 - 2.13.1 + 5.9.0 + 4.13.2 + + + ${basedir} + META-INF + + LICENSE.txt + NOTICE.txt + + + @@ -133,26 +150,6 @@ Licensed under the MIT license. See License.txt in the project root. --> maven-gpg-plugin 3.0.1 - - org.codehaus.gmavenplus - gmavenplus-plugin - 1.13.1 - - - gmavenplus-test - - compileTests - - - - - - org.codehaus.groovy - groovy-ant - 3.0.9 - - - org.sonatype.plugins nexus-staging-maven-plugin @@ -161,6 +158,73 @@ Licensed under the MIT license. See License.txt in the project root. --> + + maven-enforcer-plugin + + + enforce-versions + + enforce + + + + + 11 + + + + + + + + maven-compiler-plugin + + 11 + 11 + + + + net.ju-n.maven.plugins + checksum-maven-plugin + 1.2 + + + verify + + artifacts + + + + + + SHA-256 + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + org.sonatype.plugins nexus-staging-maven-plugin @@ -195,6 +259,9 @@ Licensed under the MIT license. See License.txt in the project root. --> + + + true @@ -222,92 +289,33 @@ Licensed under the MIT license. See License.txt in the project root. --> - - - - com.microsoft.alm - auth-common - ${project.version} - - - com.microsoft.alm - auth-core - ${project.version} - - - com.microsoft.alm - auth-secure-storage - ${project.version} - - - com.microsoft.alm - auth-providers - ${project.version} - - - - com.microsoft.alm - oauth2-useragent - ${oauth2.useragent.version} - - - - com.fasterxml.jackson.core - jackson-databind - ${jackson.version} - + + + net.java.dev.jna + jna-platform + ${jna.version} + - - org.slf4j - slf4j-api - ${slf4j.version} - - - org.slf4j - slf4j-log4j12 - ${slf4j.version} - + + org.slf4j + slf4j-api + ${slf4j.version} + - - - org.slf4j - slf4j-nop - ${slf4j.version} - test - + + org.slf4j + slf4j-nop + ${slf4j.version} + test + - - junit - junit - 4.11 - test - - - org.mockito - mockito-core - 1.7 - test - - - com.github.tomakehurst - wiremock - 1.57 - test - - - org.littleshoot - littleproxy - 1.1.0-beta1 - test - - - org.codehaus.groovy - groovy - 3.0.9 - test - - - + + junit + junit + ${junit.version} + test + + diff --git a/providers/pom.xml b/providers/pom.xml deleted file mode 100644 index 6502bc61..00000000 --- a/providers/pom.xml +++ /dev/null @@ -1,130 +0,0 @@ - - - 4.0.0 - - - com.microsoft.alm - auth-lib-parent - 0.6.5 - - auth-providers - jar - - Authenticate data providers based on different authentication backends provided by the core module - Convenience utilities that provides authentication data based on different types of authenticators from the core module - https://java.visualstudio.com/ - - - 2.6 - - - - - - ${basedir}/../common/src/main/resources - ./ - - - ${basedir}/.. - ./ - false - - LICENSE.txt - - - - - - maven-enforcer-plugin - - - enforce-versions - - enforce - - - - - 11 - - - - - - - - maven-compiler-plugin - - 11 - 11 - - - - org.apache.maven.plugins - maven-javadoc-plugin - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - - - - - com.microsoft.alm - auth-core - - - - org.glassfish.jersey.core - jersey-client - ${jersey.version} - - - org.glassfish.jersey.connectors - jersey-apache-connector - ${jersey.version} - - - - org.slf4j - slf4j-api - - - - org.slf4j - slf4j-nop - test - - - - junit - junit - test - - - org.mockito - mockito-core - test - - - - diff --git a/providers/src/main/java/com/microsoft/alm/provider/JaxrsClientProvider.java b/providers/src/main/java/com/microsoft/alm/provider/JaxrsClientProvider.java deleted file mode 100644 index 523c1c9d..00000000 --- a/providers/src/main/java/com/microsoft/alm/provider/JaxrsClientProvider.java +++ /dev/null @@ -1,294 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.provider; - -import com.microsoft.alm.auth.Authenticator; -import com.microsoft.alm.auth.PromptBehavior; -import com.microsoft.alm.helpers.SettingsHelper; -import com.microsoft.alm.secret.Credential; -import com.microsoft.alm.secret.Token; -import com.microsoft.alm.secret.TokenPair; -import com.microsoft.alm.helpers.Debug; -import com.microsoft.alm.helpers.ObjectExtensions; -import org.apache.http.auth.AuthScope; -import org.apache.http.auth.Credentials; -import org.apache.http.auth.UsernamePasswordCredentials; -import org.apache.http.client.CredentialsProvider; -import org.apache.http.impl.client.BasicCredentialsProvider; -import org.glassfish.jersey.SslConfigurator; -import org.glassfish.jersey.apache.connector.ApacheClientProperties; -import org.glassfish.jersey.apache.connector.ApacheConnectorProvider; -import org.glassfish.jersey.client.ClientConfig; -import org.glassfish.jersey.client.ClientProperties; -import org.glassfish.jersey.client.RequestEntityProcessing; -import org.glassfish.jersey.client.spi.ConnectorProvider; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import javax.ws.rs.client.Client; -import javax.ws.rs.client.ClientBuilder; -import javax.ws.rs.client.ClientRequestContext; -import javax.ws.rs.client.ClientRequestFilter; -import java.io.IOException; -import java.net.URI; - -/** - * Provides authenticated JAXRS client based on different authenticators - * - * In case of Credential and Personal Access Token type of authentication data, basic auth is used. For PAT, the user - * name is hardcoded to identify Personal Access Token authentication type instead of the user. - * - * In case of OAuth2 token, we embedded the token as a "Bearer" token in the Authorization header. - */ -public class JaxrsClientProvider { - - private static final Logger logger = LoggerFactory.getLogger(JaxrsClientProvider.class); - - private Authenticator authenticator; - - /** - * Provides authenticated JAX RS clients based on {@link Authenticator} provided - * - * @param authenticator - * an authenticator that handles generates authentication data - */ - public JaxrsClientProvider(final Authenticator authenticator) { - this.authenticator = authenticator; - } - - /** - * Get a globally authenticated JAXRS client - a client that can potentially access all accounts the user owns with - * ${@link PromptBehavior} AUTO. - * - * Be aware that a globally authenticated client DOES NOT mean the client can represent the principal and has - * permission to everything the user owns. If backed by {@link com.microsoft.alm.auth.pat.VstsPatAuthenticator} - * the client is limited to the scopes defined in the Personal Access Token. If no Personal Access {@link Token} - * exists, it will generate one with the default {@link Options}. - * - * @return client - * authenticated JAXRS client. {@code null} if authentication failed. - */ - public Client getClient() { - return getClient(PromptBehavior.AUTO, Options.getDefaultOptions()); - } - - /** - * Get a globally authenticated JAXRS client - a client that can potentially access all accounts the user owns with - * the specified {@link PromptBehavior} behavior. - * - * Be aware that a globally authenticated client DOES NOT mean the client can represent the principal and has - * permission to everything the user owns. If backed by {@link com.microsoft.alm.auth.pat.VstsPatAuthenticator} - * the client is limited to the scopes defined in the Personal Access Token. If no Personal Access {@link Token} - * exists, it will generate one with the specified {@link Options} if we allow PAT generation with the specified - * prompt behavior. - * - * @param promptBehavior - * dictates we allow prompting the user or not. In case of VstsPatAuthenticator, prompting also means generate - * a new PAT. - * @param options - * options specified by users. - * - * @return client - * authenticated JAXRS client. {@code null} if authentication failed. - */ - public Client getClient(final PromptBehavior promptBehavior, final Options options) { - Debug.Assert(promptBehavior != null, "promptBehavior cannot be null"); - Debug.Assert(options != null, "options cannot be null"); - - Client client = null; - - logger.info("Getting a jaxrs client that works across multiple accounts."); - if (authenticator.isOAuth2TokenSupported()) { - logger.debug("Getting a jaxrs client backed by OAuth2 token."); - final TokenPair tokenPair = authenticator.getOAuth2TokenPair(promptBehavior); - client = getClientWithOAuth2RequestFilter(tokenPair); - } - // Get a client backed by a global PAT - else if (authenticator.isPersonalAccessTokenSupported()) { - logger.debug("Getting a jaxrs client backed by PersonalAccessToken."); - final Token token = authenticator.getPersonalAccessToken( - options.patGenerationOptions.tokenScope, - options.patGenerationOptions.displayName, - promptBehavior); - - if (token != null) { - client = getClientWithUsernamePassword(authenticator.getAuthType(), token.Value); - } - } - - logger.info("Successfully created an authenticated client? {}", client != null); - - return client; - } - - /** - * Get an authenticated JAXRS client for the specific account URI with {@link PromptBehavior} AUTO. - * - * If backed by {@link com.microsoft.alm.auth.pat.VstsPatAuthenticator} and no PAT exists, will generate one - * with default {@link Options}. - * - * @param uri - * target uri we want to send request against - * - * @return client - * authenticated JAXRS client. {@code null} if authentication failed. - */ - public Client getClientFor(final URI uri) { - return getClientFor(uri, PromptBehavior.AUTO, Options.getDefaultOptions()); - } - - /** - * Get an authenticated JAXRS client for the specified account with the specified {@link PromptBehavior} behavior. - * - * If backed by {@link com.microsoft.alm.auth.pat.VstsPatAuthenticator} and no PAT exists, will generate one with - * the specified {@link Options} if we allow PAT generation with the specified prompt behavior. - * - * @param uri - * target uri we want to send request against - * @param promptBehavior - * dictates we allow prompting the user or not. In case of VstsPatAuthenticator, prompting also means generate - * a new PAT. - * @param options - * options specified by users. - * - * @return client - * authenticated JAXRS client. {@code null} if authentication failed. - */ - public Client getClientFor(final URI uri, final PromptBehavior promptBehavior, final Options options) { - Debug.Assert(uri != null, "uri cannot be null"); - Debug.Assert(promptBehavior != null, "promptBehavior cannot be null"); - Debug.Assert(options != null, "options cannot be null"); - - Client client = null; - logger.info("Getting a jaxrs client for uri: {}.", uri); - - if (authenticator.isCredentialSupported()) { - - logger.debug("Getting a jaxrs client backed by basic auth."); - final Credential credential = authenticator.getCredential(uri, promptBehavior); - if (credential != null) { - client = getClientWithUsernamePassword(credential.Username, credential.Password); - } - - } else if (authenticator.isOAuth2TokenSupported()) { - logger.debug("Getting a jaxrs client backed by OAuth2 token."); - - final TokenPair tokenPair = authenticator.getOAuth2TokenPair(uri, promptBehavior); - client = getClientWithOAuth2RequestFilter(tokenPair); - - } else if (authenticator.isPersonalAccessTokenSupported()) { - logger.debug("Getting a jaxrs client backed by PersonalAccessToken."); - - final Token token = authenticator.getPersonalAccessToken( - uri, - options.patGenerationOptions.tokenScope, - options.patGenerationOptions.displayName, - promptBehavior); - - if (token != null) { - client = getClientWithUsernamePassword(authenticator.getAuthType(), token.Value); - } - } - - logger.debug("Successfully created an authenticated client for uri: {}? {}", uri, client != null); - return client; - } - - private Client getClientWithOAuth2RequestFilter(final TokenPair tokenPair) { - // default Jersey client with HttpURLConnection as the connector - final Client client; - - // add proxy information - final ClientConfig clientConfig = new ClientConfig(); - addProxySettings(clientConfig); - - if (tokenPair != null && tokenPair.AccessToken != null) { - client = ClientBuilder.newClient(clientConfig); - - client.register(new ClientRequestFilter() { - @Override - public void filter(final ClientRequestContext requestContext) throws IOException { - requestContext.getHeaders().putSingle("Authorization", "Bearer " + tokenPair.AccessToken.Value); - } - }); - } else { - client = null; - } - - return client; - } - - private Client getClientWithUsernamePassword(final String username, final String password) { - final ClientConfig clientConfig = getClientConfig(username, password); - - return ClientBuilder.newClient(clientConfig); - } - - private ClientConfig getClientConfig(final String username, final String password) { - Debug.Assert(username != null, "username cannot be null"); - Debug.Assert(password != null, "password cannot be null"); - - final Credentials credentials - = new UsernamePasswordCredentials(username, password); - - final CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); - credentialsProvider.setCredentials(AuthScope.ANY, credentials); - - final ConnectorProvider connectorProvider = new ApacheConnectorProvider(); - - final ClientConfig clientConfig = new ClientConfig().connectorProvider(connectorProvider); - clientConfig.property(ApacheClientProperties.CREDENTIALS_PROVIDER, credentialsProvider); - - clientConfig.property(ApacheClientProperties.PREEMPTIVE_BASIC_AUTHENTICATION, true); - clientConfig.property(ClientProperties.REQUEST_ENTITY_PROCESSING, RequestEntityProcessing.BUFFERED); - - addProxySettings(clientConfig); - - return clientConfig; - } - - private void addProxySettings(final ClientConfig clientConfig) { - // favor http proxyHost - final String proxyHost = SettingsHelper.getInstance().getProperty("http.proxyHost"); - final String proxyPort = ObjectExtensions.coalesce( - SettingsHelper.getInstance().getProperty("http.proxyPort"), "8080"); - - if (proxyHost != null) { - final String proxyUrl = String.format("http://%s:%s", proxyHost, proxyPort); - logger.debug("Proxy is set, adding proxy: {}", proxyUrl); - - clientConfig.property(ClientProperties.PROXY_URI, proxyUrl); - - final SslConfigurator sslConfigurator = getSslConfigurator(); - if (sslConfigurator != null) { - logger.debug("Setting up ssl configurator."); - clientConfig.property(ApacheClientProperties.SSL_CONFIG, sslConfigurator); - } - } - } - - private SslConfigurator getSslConfigurator() { - final String trustStore = SettingsHelper.getInstance().getProperty("javax.net.ssl.trustStore"); - final String trustStorePassword = SettingsHelper.getInstance().getProperty("javax.net.ssl.trustStorePassword"); - - final SslConfigurator sslConfigurator; - if (trustStore != null && trustStorePassword != null) { - logger.debug("Setting up ssl configurator with trustStore: {}", trustStore); - sslConfigurator = SslConfigurator.newInstance() - .trustStoreFile(trustStore) - .trustStorePassword(trustStorePassword) - .trustStoreType("JKS") - .trustManagerFactoryAlgorithm("PKIX") - .securityProtocol("SSL"); - - } else { - logger.debug("trustStore exists? {}, trustStorePassword is specified? {}", - trustStore != null, - trustStorePassword != null); - sslConfigurator = null; - } - - return sslConfigurator; - } -} diff --git a/providers/src/main/java/com/microsoft/alm/provider/Options.java b/providers/src/main/java/com/microsoft/alm/provider/Options.java deleted file mode 100644 index a24c489b..00000000 --- a/providers/src/main/java/com/microsoft/alm/provider/Options.java +++ /dev/null @@ -1,35 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.provider; - -import com.microsoft.alm.secret.VsoTokenScope; - -public class Options { - - /** - * the only way to create this object is from the factory method - * this guarantees every options has a patGenerationOptions object - * no guarantee about the actual value inside the object though, - * and they could be null - */ - private Options(final String displayName, final VsoTokenScope scope) { - // Right now only PAT requires any sort of options at all - this.patGenerationOptions = new Options.PatGenerationOptions(); - this.patGenerationOptions.displayName = displayName; - this.patGenerationOptions.tokenScope = scope; - } - - public static Options getDefaultOptions() { - final Options options = new Options("Personal Access Token", VsoTokenScope.AllScopes); - - return options; - } - - public final PatGenerationOptions patGenerationOptions; - - public static class PatGenerationOptions { - public String displayName; - public VsoTokenScope tokenScope; - } -} diff --git a/providers/src/main/java/com/microsoft/alm/provider/UserPasswordCredentialProvider.java b/providers/src/main/java/com/microsoft/alm/provider/UserPasswordCredentialProvider.java deleted file mode 100644 index 79fb6803..00000000 --- a/providers/src/main/java/com/microsoft/alm/provider/UserPasswordCredentialProvider.java +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.provider; - -import com.microsoft.alm.auth.Authenticator; -import com.microsoft.alm.auth.PromptBehavior; -import com.microsoft.alm.secret.Credential; -import com.microsoft.alm.secret.Token; -import com.microsoft.alm.secret.TokenPair; -import com.microsoft.alm.helpers.Debug; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.net.URI; - -/** - * Provides authentication data in the form of username / password combos - * - * For OAuth2 and Personal Access Token, there are really no username. In those cases, return a hardcoded username - * that identifies the type of authentication instead of a name that identifies the user. - */ -public class UserPasswordCredentialProvider { - - private static final Logger logger = LoggerFactory.getLogger(UserPasswordCredentialProvider.class); - - private Authenticator authenticator; - - public UserPasswordCredentialProvider(final Authenticator authenticator) { - this.authenticator = authenticator; - } - - /** - * Get a global credential that works across all accounts. - * - * Noted there is no Basic Auth data that works across all accounts, hence a Basic Auth anthenticator backed - * instance would return null. - * - * This method will prompt the user if there is no global credential available. In case of PAT, it will generate - * one according to default {@link Options} - * - * @return credential object that works across all accounts (but maybe scoped in case of PATs). - * {@code null} when authentication failed. - */ - public Credential getCredential() { - return getCredential(PromptBehavior.AUTO, Options.getDefaultOptions()); - } - - /** - * Get a global credential that works across all accounts. - * - * Noted there is no Basic Auth data that works across all accounts, hence a Basic Auth anthenticator backed - * instance would return null. - * - * This method will prompt the user if there is no global credential available and the specified - * {@link PromptBehavior} allows it. In case of PAT, it will generate one according to the specified - * {@link Options} too. - * - * @param promptBehavior - * dictates we allow prompting the user or not. In case of VstsPatAuthenticator, prompting also means generate - * a new PAT. - * @param options - * options specified by users. - * - * @return credential object that works across all accounts (but maybe scoped in case of PATs). - * {@code null} when authentication failed. - */ - public Credential getCredential(final PromptBehavior promptBehavior, final Options options) { - Debug.Assert(promptBehavior != null, "promptBehavior cannot be null"); - Debug.Assert(options != null, "options cannot be null"); - - final String username = authenticator.getAuthType(); - - logger.info("Getting credential that works across multiple accounts. (OAuth2 token or PersonalAccessToken)"); - - String password = null; - if (authenticator.isOAuth2TokenSupported()) { - logger.info("Getting credential from OAuth2 token."); - final TokenPair tokenPair = authenticator.getOAuth2TokenPair(promptBehavior); - - if (tokenPair != null && tokenPair.AccessToken != null) { - password = tokenPair.AccessToken.Value; - } - - } else if (authenticator.isPersonalAccessTokenSupported()) { - logger.info("Getting credential from PersonalAccessToken."); - final Token token = authenticator.getPersonalAccessToken( - options.patGenerationOptions.tokenScope, - options.patGenerationOptions.displayName, - promptBehavior); - - if (token != null) { - password = token.Value; - } - } - - return createCreds(username, password); - } - - /** - * Get a credential that works for the specified account uri. - * - * This method will prompt the user if there is no credential available for the account. In case of PAT, it will - * generate one according to default {@link Options} - * - * @param uri - * account uri - * - * @return credential object for the specified account (but maybe scoped in case of PATs) - * {@code null} when authentication failed. - */ - public Credential getCredentialFor(final URI uri) { - return getCredentialFor(uri, PromptBehavior.AUTO, Options.getDefaultOptions()); - } - - /** - * Get a credential that works for the specified account uri. - * - * This method will prompt the user if there is no credential available for the account and the specified - * {@link PromptBehavior} allows prompting. In case of PAT, it will generate one according to the specified - * {@link Options} too. - * - * @param uri - * target uri we want to send request against - * @param promptBehavior - * dictates we allow prompting the user or not. In case of VstsPatAuthenticator, prompting also means generate - * a new PAT. - * @param options - * options specified by users. - * - * @return credential object that works across all accounts (but maybe scoped in case of PATs). - * {@code null} when authentication failed. - */ - public Credential getCredentialFor(final URI uri, final PromptBehavior promptBehavior, - final Options options) { - Debug.Assert(uri != null, "uri cannot be null"); - Debug.Assert(promptBehavior != null, "promptBehavior cannot be null"); - Debug.Assert(options != null, "options cannot be null"); - - String username = null; - String password = null; - - logger.info("Getting credential that works for uri: {}", uri); - if (authenticator.isCredentialSupported()) { - logger.info("Getting credential based on Basic Auth"); - final Credential credential = authenticator.getCredential(uri, promptBehavior); - if (credential != null) { - username = credential.Username; - password = credential.Password; - } - - } else if (authenticator.isOAuth2TokenSupported()) { - logger.info("Getting credential based on OAuth2 token"); - final TokenPair tokenPair = authenticator.getOAuth2TokenPair(promptBehavior); - - if (tokenPair != null && tokenPair.AccessToken != null) { - username = authenticator.getAuthType(); - password = tokenPair.AccessToken.Value; - } - - } else if (authenticator.isPersonalAccessTokenSupported()) { - logger.info("Getting credential based on PersonalAccessToken"); - final Token token = authenticator.getPersonalAccessToken(uri, - options.patGenerationOptions.tokenScope, - options.patGenerationOptions.displayName, - promptBehavior); - - if (token != null) { - username = authenticator.getAuthType(); - password = token.Value; - } - } - - return createCreds(username, password); - } - - private Credential createCreds(final String username, final String password) { - logger.info("Username exist? {}, password exists? {}", username != null, password != null); - return (username != null && password != null) ? new Credential(username, password) : null; - } -} diff --git a/sample/pom.xml b/sample/pom.xml index a36fed9c..a58bc323 100644 --- a/sample/pom.xml +++ b/sample/pom.xml @@ -4,18 +4,13 @@ Licensed under the MIT license. See License.txt in the project root. --> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - com.microsoft.alm - auth-lib-parent - 0.6.5 - - auth-sample + com.microsoft.a4o + credential-secure-storage-sample + 1.0.0 jar - Sample App of the Storage Library - Sample App to demonstrate how to use the Storage Library to store credentials in a store. - - https://java.visualstudio.com/ + Sample Application for the Credential Secure Storage library + Sample to demonstrate how to use the Storage Library to store credentials or token. @@ -31,20 +26,15 @@ Licensed under the MIT license. See License.txt in the project root. --> - com.microsoft.alm - auth-secure-storage - - - - org.slf4j - slf4j-api - ${slf4j.version} + com.microsoft.a4o + credential-secure-storage + 1.0.0 org.slf4j slf4j-jdk14 - ${slf4j.version} + 1.7.36 diff --git a/sample/src/main/java/com/microsoft/alm/auth/sample/App.java b/sample/src/main/java/com/microsoft/a4o/credentialstorage/sample/AppCredential.java similarity index 61% rename from sample/src/main/java/com/microsoft/alm/auth/sample/App.java rename to sample/src/main/java/com/microsoft/a4o/credentialstorage/sample/AppCredential.java index 480fac97..9e891777 100644 --- a/sample/src/main/java/com/microsoft/alm/auth/sample/App.java +++ b/sample/src/main/java/com/microsoft/a4o/credentialstorage/sample/AppCredential.java @@ -1,18 +1,18 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.auth.sample; +package com.microsoft.a4o.credentialstorage.sample; -import com.microsoft.alm.secret.Credential; -import com.microsoft.alm.storage.SecretStore; -import com.microsoft.alm.storage.StorageProvider; -import com.microsoft.alm.storage.StorageProvider.SecureOption; +import com.microsoft.a4o.credentialstorage.secret.Credential; +import com.microsoft.a4o.credentialstorage.storage.SecretStore; +import com.microsoft.a4o.credentialstorage.storage.StorageProvider; +import com.microsoft.a4o.credentialstorage.storage.StorageProvider.SecureOption; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; -public class App { +public class AppCredential { private static final String CREDENTIALS_KEY = "TestCredentials"; private static final BufferedReader INPUT = new BufferedReader(new InputStreamReader(System.in)); @@ -20,8 +20,8 @@ public static void main(String[] args) throws IOException { // Get a secure store instance final SecretStore credentialStorage = StorageProvider.getCredentialStorage(true, SecureOption.MUST); - // Get token name from the user - String credentialName = getCredentialName(CREDENTIALS_KEY); + // Get credentials name from the user + String credentialName = getCredentialName(); // Retrieve the existing credential from the store Credential storedCredential = credentialStorage.get(credentialName); @@ -37,16 +37,17 @@ public static void main(String[] args) throws IOException { // Save the credential to the store credentialStorage.add(credentialName, credential); - System.out.println("Added/Updated credentials to Credential Manager under the key: " + credentialName); + System.out.println("Added/Updated credentials under the key: " + credentialName); System.out.println(); // Retrieve the credential from the store Credential newStoredCredential = credentialStorage.get(credentialName); - System.out.println("Retrieved the updated token from Credential Manager using the key: " + credentialName); + System.out.println("Retrieved the updated credentials using the key: " + credentialName); printCredential(credentialName, newStoredCredential); - System.out.println("Remove the token from Credential Manager under the key " + credentialName + " [Y/n]?"); + // Remove credentials from the store + System.out.println("Remove the credentials under the key " + credentialName + " [Y/n]?"); if (!"n".equalsIgnoreCase(INPUT.readLine())) { credentialStorage.delete(credentialName); } @@ -54,19 +55,19 @@ public static void main(String[] args) throws IOException { private static void printCredential(String credentialName, Credential storedCredential) { if (storedCredential != null) { - System.out.println("Retrieved current credentials from Credential Manager using the key: " + credentialName); - System.out.println(" Username: " + storedCredential.Username); - System.out.println(" Password: " + storedCredential.Password); + System.out.println("Retrieved the existing credentials using the key: " + credentialName); + System.out.println(" Username: " + storedCredential.getUsername()); + System.out.println(" Password: " + storedCredential.getPassword()); } else { System.out.println("No stored credentials under the key: " + credentialName); } System.out.println(); } - private static String getCredentialName(String defaultCredentialName) throws IOException { - System.out.print("Enter token name [" + defaultCredentialName + "]: "); - String tokenName = INPUT.readLine(); - if (tokenName == null || tokenName.isEmpty()) tokenName = defaultCredentialName; - return tokenName; + private static String getCredentialName() throws IOException { + System.out.print("Enter credentials name [" + CREDENTIALS_KEY + "]: "); + String credentialsName = INPUT.readLine(); + if (credentialsName == null || credentialsName.isEmpty()) credentialsName = CREDENTIALS_KEY; + return credentialsName; } } diff --git a/sample/src/main/java/com/microsoft/a4o/credentialstorage/sample/AppToken.java b/sample/src/main/java/com/microsoft/a4o/credentialstorage/sample/AppToken.java new file mode 100644 index 00000000..0055594f --- /dev/null +++ b/sample/src/main/java/com/microsoft/a4o/credentialstorage/sample/AppToken.java @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root. + +package com.microsoft.a4o.credentialstorage.sample; + +import com.microsoft.a4o.credentialstorage.secret.Token; +import com.microsoft.a4o.credentialstorage.secret.TokenType; +import com.microsoft.a4o.credentialstorage.storage.SecretStore; +import com.microsoft.a4o.credentialstorage.storage.StorageProvider; +import com.microsoft.a4o.credentialstorage.storage.StorageProvider.SecureOption; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +public class AppToken { + private static final String TOKEN_KEY = "TestToken"; + private static final BufferedReader INPUT = new BufferedReader(new InputStreamReader(System.in)); + + public static void main(String[] args) throws IOException { + // Get a secure store instance + final SecretStore tokenStorage = StorageProvider.getTokenStorage(true, SecureOption.MUST); + + // Get token name from the user + String tokenName = getTokenName(); + + // Retrieve the existing token from the store + Token storedToken = tokenStorage.get(tokenName); + printToken(tokenName, storedToken); + + // Create a new token instance from user input + System.out.println("Enter token value: "); + String tokenValue = INPUT.readLine(); + Token token = new Token(tokenValue, TokenType.Personal); + + // Save the token to the store + tokenStorage.add(tokenName, token); + + System.out.println("Added/Updated token under the key: " + tokenName); + System.out.println(); + + // Retrieve new token from the store + Token newStoredToken = tokenStorage.get(tokenName); + + System.out.println("Retrieved the updated token using the key: " + tokenName); + printToken(tokenName, newStoredToken); + + // Remove token from the store + System.out.println("Remove the token under the key " + tokenName + " [Y/n]?"); + if (!"n".equalsIgnoreCase(INPUT.readLine())) { + tokenStorage.delete(tokenName); + } + } + + private static void printToken(String tokenName, Token storedToken) { + if (storedToken != null) { + System.out.println("Retrieved the existing token using the key: " + tokenName); + System.out.println(" Token: " + storedToken.getValue() + " (Type: " + storedToken.getType() + ")"); + } else { + System.out.println("No stored token under the key: " + tokenName); + } + System.out.println(); + } + + private static String getTokenName() throws IOException { + System.out.print("Enter token name [" + TOKEN_KEY + "]: "); + String tokenName = INPUT.readLine(); + if (tokenName == null || tokenName.isEmpty()) tokenName = TOKEN_KEY; + return tokenName; + } +} diff --git a/sample/src/main/java/com/microsoft/a4o/credentialstorage/sample/AppTokenPair.java b/sample/src/main/java/com/microsoft/a4o/credentialstorage/sample/AppTokenPair.java new file mode 100644 index 00000000..734f3568 --- /dev/null +++ b/sample/src/main/java/com/microsoft/a4o/credentialstorage/sample/AppTokenPair.java @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root. + +package com.microsoft.a4o.credentialstorage.sample; + +import com.microsoft.a4o.credentialstorage.secret.TokenPair; +import com.microsoft.a4o.credentialstorage.storage.SecretStore; +import com.microsoft.a4o.credentialstorage.storage.StorageProvider; +import com.microsoft.a4o.credentialstorage.storage.StorageProvider.SecureOption; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +public class AppTokenPair { + private static final String TOKEN_PAIR_KEY = "TestTokenPair"; + private static final BufferedReader INPUT = new BufferedReader(new InputStreamReader(System.in)); + + public static void main(String[] args) throws IOException { + // Get a secure store instance + final SecretStore tokenPairStorage = StorageProvider.getTokenPairStorage(true, SecureOption.MUST); + + // Get token pair name from the user + String tokenPairName = getTokenPairName(); + + // Retrieve the existing token pair from the store + TokenPair storedTokenPair = tokenPairStorage.get(tokenPairName); + printTokenPair(tokenPairName, storedTokenPair); + + // Create a new token pair instance from user input + System.out.println("Enter access token value: "); + String accessTokenValue = INPUT.readLine(); + System.out.println("Enter refresh token value: "); + String refreshTokenValue = INPUT.readLine(); + TokenPair tokenPair = new TokenPair(accessTokenValue, refreshTokenValue); + + // Save the token pair to the store + tokenPairStorage.add(tokenPairName, tokenPair); + + System.out.println("Added/Updated token pair under the key: " + tokenPairName); + System.out.println(); + + // Retrieve new token pair from the store + TokenPair newStoredTokenPair = tokenPairStorage.get(tokenPairName); + + System.out.println("Retrieved the updated token pair using the key: " + tokenPairName); + printTokenPair(tokenPairName, newStoredTokenPair); + + // Remove token pair from the store + System.out.println("Remove the token pair under the key " + tokenPairName + " [Y/n]?"); + if (!"n".equalsIgnoreCase(INPUT.readLine())) { + tokenPairStorage.delete(tokenPairName); + } + } + + private static void printTokenPair(String tokenPairName, TokenPair storedTokenPair) { + if (storedTokenPair != null) { + System.out.println("Retrieved the existing token pair using the key: " + tokenPairName); + System.out.println(" Access token: " + storedTokenPair.getAccessToken().getValue() + " (Type: " + storedTokenPair.getAccessToken().getType() + ")"); + System.out.println(" Refresh token: " + storedTokenPair.getRefreshToken().getValue() + " (Type: " + storedTokenPair.getRefreshToken().getType() + ")"); + } else { + System.out.println("No stored token pair under the key: " + tokenPairName); + } + System.out.println(); + } + + private static String getTokenPairName() throws IOException { + System.out.print("Enter token pair name [" + TOKEN_PAIR_KEY + "]: "); + String tokenPairName = INPUT.readLine(); + if (tokenPairName == null || tokenPairName.isEmpty()) tokenPairName = TOKEN_PAIR_KEY; + return tokenPairName; + } +} diff --git a/src/main/java/com/microsoft/a4o/credentialstorage/helpers/StringHelper.java b/src/main/java/com/microsoft/a4o/credentialstorage/helpers/StringHelper.java new file mode 100644 index 00000000..1dd13e12 --- /dev/null +++ b/src/main/java/com/microsoft/a4o/credentialstorage/helpers/StringHelper.java @@ -0,0 +1,101 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root. + +package com.microsoft.a4o.credentialstorage.helpers; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.Objects; +import java.util.function.Function; + +public class StringHelper { + private static final Charset UTF8 = StandardCharsets.UTF_8; + + private static final Charset UTF16LE = StandardCharsets.UTF_16LE; + + public static final String Empty = ""; + + public static boolean isNullOrWhiteSpace(final String s) { + return null == s || (s.trim().length() == 0); + } + + /** + * Concatenates the specified elements of a string array, + * using the specified separator between each element. + * + * @param separator The string to use as a separator. + * separator is included in the returned string only if value has more than one element. + * @param value An array that contains the elements to concatenate. + * @param startIndex The first element in value to use. + * @param count The number of elements of value to use. + * @param processor A callback that gets to intercept and modify elements before they are inserted. + * @return A string that consists of the strings in value delimited by the separator string. + * -or- + * {@link StringHelper#Empty} if count is zero, value has no elements, + * or separator and all the elements of value are {@link StringHelper#Empty}. + */ + public static String join(final String separator, final String[] value, final int startIndex, final int count, + final Function processor) { + if (value == null) + throw new IllegalArgumentException("value is null"); + if (startIndex < 0) + throw new IllegalArgumentException("startIndex is less than 0"); + if (count < 0) + throw new IllegalArgumentException("count is less than 0"); + if (startIndex + count > value.length) + throw new IllegalArgumentException("startIndex + count is greater than the number of elements in value"); + + // "If separator is null, an empty string ( String.Empty) is used instead." + final String sep = Objects.requireNonNullElse(separator, StringHelper.Empty); + + final StringBuilder result = new StringBuilder(); + + if (value.length > 0 && count > 0) { + String element = Objects.requireNonNullElse(value[startIndex], StringHelper.Empty); + if (processor != null) { + element = processor.apply(element); + } + result.append(element); + for (int i = startIndex + 1; i < startIndex + count; i++) { + result.append(sep); + element = Objects.requireNonNullElse(value[i], StringHelper.Empty); + if (processor != null) { + element = processor.apply(element); + } + result.append(element); + } + } + + return result.toString(); + } + + /** + * Encodes all the characters in the specified string into a sequence of UTF-8 bytes. + * + * @param value The string containing the characters to encode. + * @return A byte array containing the results of encoding the specified set of characters. + */ + public static byte[] UTF8GetBytes(final String value) { + return value.getBytes(UTF8); + } + + /** + * Encodes all the characters in the specified string into a sequence of UTF-16LE bytes. + * + * @param value The string containing the characters to encode. + * @return A byte array containing the results of encoding the specified set of characters. + */ + public static byte[] UTF16LEGetBytes(final String value) { + return value.getBytes(UTF16LE); + } + + /** + * Decodes all the bytes in the specified byte array into a string. + * + * @param bytes The byte array containing the sequence of bytes to decode. + * @return A string that contains the results of decoding the specified sequence of bytes. + */ + public static String UTF16LEGetString(final byte[] bytes) { + return new String(bytes, UTF16LE); + } +} \ No newline at end of file diff --git a/common/src/main/java/com/microsoft/alm/helpers/SystemHelper.java b/src/main/java/com/microsoft/a4o/credentialstorage/helpers/SystemHelper.java similarity index 94% rename from common/src/main/java/com/microsoft/alm/helpers/SystemHelper.java rename to src/main/java/com/microsoft/a4o/credentialstorage/helpers/SystemHelper.java index 3a5b9880..49c64a62 100644 --- a/common/src/main/java/com/microsoft/alm/helpers/SystemHelper.java +++ b/src/main/java/com/microsoft/a4o/credentialstorage/helpers/SystemHelper.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.helpers; +package com.microsoft.a4o.credentialstorage.helpers; /** * System utilities diff --git a/common/src/main/java/com/microsoft/alm/helpers/XmlHelper.java b/src/main/java/com/microsoft/a4o/credentialstorage/helpers/XmlHelper.java similarity index 97% rename from common/src/main/java/com/microsoft/alm/helpers/XmlHelper.java rename to src/main/java/com/microsoft/a4o/credentialstorage/helpers/XmlHelper.java index 23a9ebaa..65927783 100644 --- a/common/src/main/java/com/microsoft/alm/helpers/XmlHelper.java +++ b/src/main/java/com/microsoft/a4o/credentialstorage/helpers/XmlHelper.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.helpers; +package com.microsoft.a4o.credentialstorage.helpers; import org.w3c.dom.Document; import org.w3c.dom.Node; diff --git a/src/main/java/com/microsoft/a4o/credentialstorage/secret/Credential.java b/src/main/java/com/microsoft/a4o/credentialstorage/secret/Credential.java new file mode 100644 index 00000000..46bec77e --- /dev/null +++ b/src/main/java/com/microsoft/a4o/credentialstorage/secret/Credential.java @@ -0,0 +1,104 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root. + +package com.microsoft.a4o.credentialstorage.secret; + +import com.microsoft.a4o.credentialstorage.helpers.StringHelper; + +import java.util.Objects; + +/** + * Credential for user authentication. + */ +public final class Credential extends Secret { + public static final int USERNAME_MAX_LENGTH = 511; + public static final int PASSWORD_MAX_LENGTH = 2047; + + private final String username; + private final String password; + + /** + * Creates a credential object with a username and password pair. + * + * @param username The username value of the {@link Credential}. + * @param password The password value of the {@link Credential}. + */ + public Credential(final String username, final String password) { + this.username = Objects.requireNonNullElse(username, StringHelper.Empty); + this.password = Objects.requireNonNullElse(password, StringHelper.Empty); + } + + /** + * Creates a credential object with only a username. + * + * @param username The username value of the {@link Credential}. + */ + public Credential(final String username) { + this(username, StringHelper.Empty); + } + + /** + * Unique identifier of the user. + * @return username + */ + public String getUsername() { + return username; + } + + /** + * Secret related to the username. + * @return secret + */ + public String getPassword() { + return password; + } + + /** + * Compares an object to this {@link Credential} for equality. + * + * @param obj The object to compare. + * @return True if equal; false otherwise. + */ + @Override + public boolean equals(final Object obj) { + return operatorEquals(this, obj instanceof Credential ? ((Credential) obj) : null); + } + + /** + * Gets a hash code based on the contents of the {@link Credential}. + * + * @return 32-bit hash code. + */ + @Override + public int hashCode() { + return username.hashCode() + 7 * password.hashCode(); + } + + public static void validate(final Credential credentials) { + if (credentials == null) + throw new IllegalArgumentException("The credentials parameter cannot be null"); + if (credentials.password.length() > PASSWORD_MAX_LENGTH) + throw new IllegalArgumentException(String.format("The Password field of the credentials parameter cannot " + + "be longer than %1$d characters.", PASSWORD_MAX_LENGTH)); + if (credentials.username.length() > USERNAME_MAX_LENGTH) + throw new IllegalArgumentException(String.format("The Username field of the credentials parameter cannot " + + "be longer than %1$d characters.", USERNAME_MAX_LENGTH)); + } + + /** + * Compares two credentials for equality. + * + * @param credential1 Credential to compare. + * @param credential2 Credential to compare. + * @return True if equal; false otherwise. + */ + public static boolean operatorEquals(final Credential credential1, final Credential credential2) { + if (credential1 == credential2) + return true; + if ((credential1 == null) || (null == credential2)) + return false; + + return credential1.username.equals(credential2.username) + && credential1.password.equals(credential2.password); + } +} diff --git a/src/main/java/com/microsoft/a4o/credentialstorage/secret/Secret.java b/src/main/java/com/microsoft/a4o/credentialstorage/secret/Secret.java new file mode 100644 index 00000000..8f1481cd --- /dev/null +++ b/src/main/java/com/microsoft/a4o/credentialstorage/secret/Secret.java @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root. + +package com.microsoft.a4o.credentialstorage.secret; + +public abstract class Secret { +} diff --git a/src/main/java/com/microsoft/a4o/credentialstorage/secret/Token.java b/src/main/java/com/microsoft/a4o/credentialstorage/secret/Token.java new file mode 100644 index 00000000..a68eb99b --- /dev/null +++ b/src/main/java/com/microsoft/a4o/credentialstorage/secret/Token.java @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root. + +package com.microsoft.a4o.credentialstorage.secret; + +import com.microsoft.a4o.credentialstorage.helpers.StringHelper; + +import java.util.Objects; +import java.util.UUID; + +/** + * A security token, usually acquired by some authentication and identity services. + */ +public class Token extends Secret { + private static final UUID EMPTY_UUID = new UUID(0, 0); + + /** + * The type of the security token. + */ + private final TokenType type; + + /** + * The raw contents of the token. + */ + private final String value; + + /** + * The target identity for the security token. + */ + private final UUID targetIdentity; + + public Token(final String value, final TokenType type, final UUID targetIdentity) { + if (StringHelper.isNullOrWhiteSpace(value)) { + throw new IllegalArgumentException("The value parameter is null or invalid"); + } + Objects.requireNonNull(type, "The type parameter is null"); + Objects.requireNonNull(targetIdentity, "The targetIdentity parameter is null"); + + this.type = type; + this.value = value; + this.targetIdentity = targetIdentity; + } + + public Token(final String value, final TokenType type) { + this(value, type, EMPTY_UUID); + } + + public TokenType getType() { + return type; + } + + public String getValue() { + return value; + } + + public UUID getTargetIdentity() { + return targetIdentity; + } + + /** + * Compares an object to this {@link Token} for equality. + * + * @param obj The object to compare. + * @return True is equal; false otherwise. + */ + @Override + public boolean equals(final Object obj) { + return operatorEquals(this, obj instanceof Token ? ((Token) obj) : null); + } + + /** + * Gets a hash code based on the contents of the token. + * + * @return 32-bit hash code. + */ + @Override + public int hashCode() { + return type.ordinal() * value.hashCode(); + } + + public static void validate(final Token token) { + if (token == null) + throw new IllegalArgumentException("The `token` parameter is null or invalid."); + if (StringHelper.isNullOrWhiteSpace(token.value)) + throw new IllegalArgumentException("The value of the `token` cannot be null or empty."); + if (token.value.length() > Credential.PASSWORD_MAX_LENGTH) + throw new IllegalArgumentException(String.format("The value of the `token` cannot be longer than %1$d " + + "characters.", Credential.PASSWORD_MAX_LENGTH)); + } + + /** + * Compares two tokens for equality. + * + * @param token1 Token to compare. + * @param token2 Token to compare. + * @return True if equal; false otherwise. + */ + public static boolean operatorEquals(final Token token1, final Token token2) { + if (token1 == token2) + return true; + if ((token1 == null) || (null == token2)) + return false; + + return token1.type == token2.type + && token1.value.equalsIgnoreCase(token2.value); + } +} diff --git a/src/main/java/com/microsoft/a4o/credentialstorage/secret/TokenPair.java b/src/main/java/com/microsoft/a4o/credentialstorage/secret/TokenPair.java new file mode 100644 index 00000000..ca3dbdfa --- /dev/null +++ b/src/main/java/com/microsoft/a4o/credentialstorage/secret/TokenPair.java @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root. + +package com.microsoft.a4o.credentialstorage.secret; + +import com.microsoft.a4o.credentialstorage.helpers.StringHelper; + +import java.util.Collections; +import java.util.Map; + +public class TokenPair extends Secret { + /** + * Access token, used to grant access to resources. + */ + private final Token accessToken; + + /** + * Refresh token, used to grant new access tokens. + */ + private final Token refreshToken; + + /** + * Additional token parameters. + */ + private final Map parameters; + + /** + * Creates a new {@link TokenPair} from raw access and refresh token data. + * + * @param accessToken The base64 encoded value of the access token's raw data + * @param refreshToken The base64 encoded value of the refresh token's raw data + */ + public TokenPair(final String accessToken, final String refreshToken) { + if (StringHelper.isNullOrWhiteSpace(accessToken)) { + throw new IllegalArgumentException("The accessToken parameter is null or invalid."); + } + if (StringHelper.isNullOrWhiteSpace(refreshToken)) { + throw new IllegalArgumentException("The refreshToken parameter is null or invalid."); + } + + this.accessToken = new Token(accessToken, TokenType.Access); + this.refreshToken = new Token(refreshToken, TokenType.Refresh); + this.parameters = Collections.emptyMap(); + } + + public Token getAccessToken() { + return accessToken; + } + + public Token getRefreshToken() { + return refreshToken; + } + + public Map getParameters() { + return parameters; + } + + /** + * Compares an object to this. + * + * @param object The object to compare. + * @return True if equal; false otherwise + */ + @Override + public boolean equals(final Object object) { + return operatorEquals(this, object instanceof TokenPair ? ((TokenPair) object) : null); + } + + /** + * Gets a hash code based on the contents of the {@link TokenPair}. + * + * @return 32-bit hash code. + */ + @Override + public int hashCode() { + return accessToken.hashCode() * refreshToken.hashCode(); + } + + /** + * Compares two {@link TokenPair} for equality. + * + * @param pair1 {@link TokenPair} to compare. + * @param pair2 {@link TokenPair} to compare. + * @return True if equal; false otherwise. + */ + public static boolean operatorEquals(final TokenPair pair1, final TokenPair pair2) { + if (pair1 == pair2) + return true; + if ((pair1 == null) || (null == pair2)) + return false; + + return Token.operatorEquals(pair1.accessToken, pair2.accessToken) + && Token.operatorEquals(pair1.refreshToken, pair2.refreshToken); + } +} diff --git a/src/main/java/com/microsoft/a4o/credentialstorage/secret/TokenType.java b/src/main/java/com/microsoft/a4o/credentialstorage/secret/TokenType.java new file mode 100644 index 00000000..506bd362 --- /dev/null +++ b/src/main/java/com/microsoft/a4o/credentialstorage/secret/TokenType.java @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root. + +package com.microsoft.a4o.credentialstorage.secret; + +public enum TokenType { + Unknown(null), + /** + * Access Token + */ + Access("Access Token"), + /** + * Refresh Token + */ + Refresh("Refresh Token"), + /** + * Personal Access Token, can be compact or not. + */ + Personal("Personal Access Token"), + /** + * Federated Authentication (aka FedAuth) Token + */ + Federated("Federated Authentication Token"), + /** + * Used only for testing + */ + Test("Test-only Token"); + + private final String description; + + TokenType(final String description) { + this.description = description; + } + + public String getDescription() { + return description; + } +} diff --git a/common/src/main/java/com/microsoft/alm/storage/InsecureInMemoryStore.java b/src/main/java/com/microsoft/a4o/credentialstorage/storage/InsecureInMemoryStore.java similarity index 84% rename from common/src/main/java/com/microsoft/alm/storage/InsecureInMemoryStore.java rename to src/main/java/com/microsoft/a4o/credentialstorage/storage/InsecureInMemoryStore.java index 2469282a..45e1bcc5 100644 --- a/common/src/main/java/com/microsoft/alm/storage/InsecureInMemoryStore.java +++ b/src/main/java/com/microsoft/a4o/credentialstorage/storage/InsecureInMemoryStore.java @@ -1,9 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage; +package com.microsoft.a4o.credentialstorage.storage; -import com.microsoft.alm.secret.Secret; +import com.microsoft.a4o.credentialstorage.secret.Secret; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -13,7 +13,7 @@ public class InsecureInMemoryStore implements SecretStore { private final ConcurrentMap store; public InsecureInMemoryStore() { - store = new ConcurrentHashMap(); + store = new ConcurrentHashMap<>(); } @Override diff --git a/common/src/main/java/com/microsoft/alm/storage/SecretStore.java b/src/main/java/com/microsoft/a4o/credentialstorage/storage/SecretStore.java similarity index 93% rename from common/src/main/java/com/microsoft/alm/storage/SecretStore.java rename to src/main/java/com/microsoft/a4o/credentialstorage/storage/SecretStore.java index e675c309..34b7e15a 100644 --- a/common/src/main/java/com/microsoft/alm/storage/SecretStore.java +++ b/src/main/java/com/microsoft/a4o/credentialstorage/storage/SecretStore.java @@ -1,9 +1,9 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage; +package com.microsoft.a4o.credentialstorage.storage; -import com.microsoft.alm.secret.Secret; +import com.microsoft.a4o.credentialstorage.secret.Secret; /** * Secret store to hold the credentials. diff --git a/storage/src/main/java/com/microsoft/alm/storage/StorageProvider.java b/src/main/java/com/microsoft/a4o/credentialstorage/storage/StorageProvider.java similarity index 75% rename from storage/src/main/java/com/microsoft/alm/storage/StorageProvider.java rename to src/main/java/com/microsoft/a4o/credentialstorage/storage/StorageProvider.java index 1d0ef58c..3f36937c 100644 --- a/storage/src/main/java/com/microsoft/alm/storage/StorageProvider.java +++ b/src/main/java/com/microsoft/a4o/credentialstorage/storage/StorageProvider.java @@ -1,33 +1,33 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage; - -import com.microsoft.alm.helpers.Debug; -import com.microsoft.alm.helpers.SystemHelper; -import com.microsoft.alm.secret.Credential; -import com.microsoft.alm.secret.Secret; -import com.microsoft.alm.secret.Token; -import com.microsoft.alm.secret.TokenPair; -import com.microsoft.alm.storage.macosx.KeychainSecurityBackedCredentialStore; -import com.microsoft.alm.storage.macosx.KeychainSecurityBackedTokenPairStore; -import com.microsoft.alm.storage.macosx.KeychainSecurityBackedTokenStore; -import com.microsoft.alm.storage.posix.GnomeKeyringBackedCredentialStore; -import com.microsoft.alm.storage.posix.GnomeKeyringBackedTokenPairStore; -import com.microsoft.alm.storage.posix.GnomeKeyringBackedTokenStore; -import com.microsoft.alm.storage.posix.internal.GnomeKeyringBackedSecureStore; -import com.microsoft.alm.storage.windows.CredManagerBackedCredentialStore; -import com.microsoft.alm.storage.windows.CredManagerBackedTokenPairStore; -import com.microsoft.alm.storage.windows.CredManagerBackedTokenStore; +package com.microsoft.a4o.credentialstorage.storage; + +import com.microsoft.a4o.credentialstorage.helpers.SystemHelper; +import com.microsoft.a4o.credentialstorage.secret.Credential; +import com.microsoft.a4o.credentialstorage.secret.Secret; +import com.microsoft.a4o.credentialstorage.secret.Token; +import com.microsoft.a4o.credentialstorage.secret.TokenPair; +import com.microsoft.a4o.credentialstorage.storage.macosx.KeychainSecurityBackedCredentialStore; +import com.microsoft.a4o.credentialstorage.storage.macosx.KeychainSecurityBackedTokenPairStore; +import com.microsoft.a4o.credentialstorage.storage.macosx.KeychainSecurityBackedTokenStore; +import com.microsoft.a4o.credentialstorage.storage.posix.GnomeKeyringBackedCredentialStore; +import com.microsoft.a4o.credentialstorage.storage.posix.GnomeKeyringBackedTokenPairStore; +import com.microsoft.a4o.credentialstorage.storage.posix.GnomeKeyringBackedTokenStore; +import com.microsoft.a4o.credentialstorage.storage.posix.internal.GnomeKeyringBackedSecureStore; +import com.microsoft.a4o.credentialstorage.storage.windows.CredManagerBackedCredentialStore; +import com.microsoft.a4o.credentialstorage.storage.windows.CredManagerBackedTokenPairStore; +import com.microsoft.a4o.credentialstorage.storage.windows.CredManagerBackedTokenStore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.ArrayList; import java.util.List; +import java.util.Objects; public class StorageProvider { - private static Logger logger = LoggerFactory.getLogger(StorageProvider.class); + private static final Logger logger = LoggerFactory.getLogger(StorageProvider.class); public enum SecureOption { /** @@ -52,9 +52,9 @@ public enum SecureOption { private static final List> PERSISTED_CREDENTIAL_STORE_CANDIDATES; static { - List> tokenStoreCandidates = new ArrayList>(); - List> tokenPairStoreCandidates = new ArrayList>(); - List> credentialStoreCandidates = new ArrayList>(); + List> tokenStoreCandidates = new ArrayList<>(); + List> tokenPairStoreCandidates = new ArrayList<>(); + List> credentialStoreCandidates = new ArrayList<>(); if (SystemHelper.isWindows()) { tokenStoreCandidates.add(new CredManagerBackedTokenStore()); @@ -74,24 +74,21 @@ public enum SecureOption { tokenPairStoreCandidates.add(new GnomeKeyringBackedTokenPairStore()); } - tokenStoreCandidates.add(new InsecureFileBackedTokenStore()); - credentialStoreCandidates.add(new InsecureFileBackedCredentialStore()); - PERSISTED_TOKEN_STORE_CANDIDATES = tokenStoreCandidates; PERSISTED_TOKENPAIR_STORE_CANDIDATES = tokenPairStoreCandidates; PERSISTED_CREDENTIAL_STORE_CANDIDATES = credentialStoreCandidates; } public static SecretStore getTokenStorage(final boolean persist, final SecureOption secureOption) { - Debug.Assert(secureOption != null, "secureOption cannot be null"); + Objects.requireNonNull(secureOption, "secureOption cannot be null"); logger.info("Getting a {} token store that {} be secure", persist ? "persistent" : "non-persistent", secureOption == SecureOption.MUST ? "must" : "could"); - final NonPersistentStoreGenerator inMemoryStoreGenerator = new NonPersistentStoreGenerator() { + final NonPersistentStoreGenerator inMemoryStoreGenerator = new NonPersistentStoreGenerator<>() { @Override public SecretStore getInsecureNonPersistentStore() { - return new InsecureInMemoryStore(); + return new InsecureInMemoryStore<>(); } @Override @@ -105,15 +102,15 @@ public SecretStore getSecureNonPersistentStore() { } public static SecretStore getTokenPairStorage(final boolean persist, final SecureOption secureOption) { - Debug.Assert(secureOption != null, "secureOption cannot be null"); + Objects.requireNonNull(secureOption, "secureOption cannot be null"); logger.info("Getting a {} tokenPair store that {} be secure", persist ? "persistent" : "non-persistent", secureOption == SecureOption.MUST ? "must" : "could"); - final NonPersistentStoreGenerator inMemoryStoreGenerator = new NonPersistentStoreGenerator() { + final NonPersistentStoreGenerator inMemoryStoreGenerator = new NonPersistentStoreGenerator<>() { @Override public SecretStore getInsecureNonPersistentStore() { - return new InsecureInMemoryStore(); + return new InsecureInMemoryStore<>(); } @Override @@ -127,15 +124,15 @@ public SecretStore getSecureNonPersistentStore() { } public static SecretStore getCredentialStorage(final boolean persist, final SecureOption secureOption) { - Debug.Assert(secureOption != null, "secureOption cannot be null"); + Objects.requireNonNull(secureOption, "secureOption cannot be null"); logger.info("Getting a {} credential store that {} be secure", persist ? "persistent" : "non-persistent", secureOption == SecureOption.MUST ? "must" : "could"); - final NonPersistentStoreGenerator inMemoryStoreGenerator = new NonPersistentStoreGenerator() { + final NonPersistentStoreGenerator inMemoryStoreGenerator = new NonPersistentStoreGenerator<>() { @Override public SecretStore getInsecureNonPersistentStore() { - return new InsecureInMemoryStore(); + return new InsecureInMemoryStore<>(); } @Override @@ -177,8 +174,8 @@ static SecretStore getStore(final boolean persist, final SecureOption secureOption, final List> stores, final NonPersistentStoreGenerator nonPersistentStoreGenerator) { - Debug.Assert(nonPersistentStoreGenerator != null, "nonPersistentStoreGenerator cannot be null."); - Debug.Assert(stores != null, "stores cannot be null."); + Objects.requireNonNull(nonPersistentStoreGenerator, "nonPersistentStoreGenerator cannot be null."); + Objects.requireNonNull(stores, "stores cannot be null."); SecretStore candidate; if (persist) { diff --git a/storage/src/main/java/com/microsoft/alm/storage/macosx/KeychainSecurityBackedCredentialStore.java b/src/main/java/com/microsoft/a4o/credentialstorage/storage/macosx/KeychainSecurityBackedCredentialStore.java similarity index 81% rename from storage/src/main/java/com/microsoft/alm/storage/macosx/KeychainSecurityBackedCredentialStore.java rename to src/main/java/com/microsoft/a4o/credentialstorage/storage/macosx/KeychainSecurityBackedCredentialStore.java index 5b3027bf..1fabf4e0 100644 --- a/storage/src/main/java/com/microsoft/alm/storage/macosx/KeychainSecurityBackedCredentialStore.java +++ b/src/main/java/com/microsoft/a4o/credentialstorage/storage/macosx/KeychainSecurityBackedCredentialStore.java @@ -1,10 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.macosx; +package com.microsoft.a4o.credentialstorage.storage.macosx; -import com.microsoft.alm.secret.Credential; -import com.microsoft.alm.storage.SecretStore; +import com.microsoft.a4o.credentialstorage.storage.SecretStore; +import com.microsoft.a4o.credentialstorage.secret.Credential; public class KeychainSecurityBackedCredentialStore extends KeychainSecurityCliStore implements SecretStore { diff --git a/storage/src/main/java/com/microsoft/alm/storage/macosx/KeychainSecurityBackedTokenPairStore.java b/src/main/java/com/microsoft/a4o/credentialstorage/storage/macosx/KeychainSecurityBackedTokenPairStore.java similarity index 82% rename from storage/src/main/java/com/microsoft/alm/storage/macosx/KeychainSecurityBackedTokenPairStore.java rename to src/main/java/com/microsoft/a4o/credentialstorage/storage/macosx/KeychainSecurityBackedTokenPairStore.java index 5fc8bdc2..b2c78bb4 100644 --- a/storage/src/main/java/com/microsoft/alm/storage/macosx/KeychainSecurityBackedTokenPairStore.java +++ b/src/main/java/com/microsoft/a4o/credentialstorage/storage/macosx/KeychainSecurityBackedTokenPairStore.java @@ -1,10 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.macosx; +package com.microsoft.a4o.credentialstorage.storage.macosx; -import com.microsoft.alm.secret.TokenPair; -import com.microsoft.alm.storage.SecretStore; +import com.microsoft.a4o.credentialstorage.storage.SecretStore; +import com.microsoft.a4o.credentialstorage.secret.TokenPair; public class KeychainSecurityBackedTokenPairStore extends KeychainSecurityCliStore implements SecretStore { diff --git a/storage/src/main/java/com/microsoft/alm/storage/macosx/KeychainSecurityBackedTokenStore.java b/src/main/java/com/microsoft/a4o/credentialstorage/storage/macosx/KeychainSecurityBackedTokenStore.java similarity index 80% rename from storage/src/main/java/com/microsoft/alm/storage/macosx/KeychainSecurityBackedTokenStore.java rename to src/main/java/com/microsoft/a4o/credentialstorage/storage/macosx/KeychainSecurityBackedTokenStore.java index 0f8e4397..b883ed45 100644 --- a/storage/src/main/java/com/microsoft/alm/storage/macosx/KeychainSecurityBackedTokenStore.java +++ b/src/main/java/com/microsoft/a4o/credentialstorage/storage/macosx/KeychainSecurityBackedTokenStore.java @@ -1,10 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.macosx; +package com.microsoft.a4o.credentialstorage.storage.macosx; -import com.microsoft.alm.secret.Token; -import com.microsoft.alm.storage.SecretStore; +import com.microsoft.a4o.credentialstorage.storage.SecretStore; +import com.microsoft.a4o.credentialstorage.secret.Token; public class KeychainSecurityBackedTokenStore extends KeychainSecurityCliStore implements SecretStore { diff --git a/storage/src/main/java/com/microsoft/alm/storage/macosx/KeychainSecurityCliStore.java b/src/main/java/com/microsoft/a4o/credentialstorage/storage/macosx/KeychainSecurityCliStore.java similarity index 62% rename from storage/src/main/java/com/microsoft/alm/storage/macosx/KeychainSecurityCliStore.java rename to src/main/java/com/microsoft/a4o/credentialstorage/storage/macosx/KeychainSecurityCliStore.java index 344d7823..3a661665 100644 --- a/storage/src/main/java/com/microsoft/alm/storage/macosx/KeychainSecurityCliStore.java +++ b/src/main/java/com/microsoft/a4o/credentialstorage/storage/macosx/KeychainSecurityCliStore.java @@ -1,39 +1,35 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.macosx; +package com.microsoft.a4o.credentialstorage.storage.macosx; - -import com.microsoft.alm.helpers.Func; -import com.microsoft.alm.helpers.IOHelper; -import com.microsoft.alm.helpers.StringHelper; -import com.microsoft.alm.oauth2.useragent.subprocess.DefaultProcessFactory; -import com.microsoft.alm.oauth2.useragent.subprocess.ProcessCoordinator; -import com.microsoft.alm.oauth2.useragent.subprocess.TestableProcess; -import com.microsoft.alm.oauth2.useragent.subprocess.TestableProcessFactory; -import com.microsoft.alm.secret.Credential; -import com.microsoft.alm.secret.Token; -import com.microsoft.alm.secret.TokenPair; +import com.microsoft.a4o.credentialstorage.helpers.StringHelper; +import com.microsoft.a4o.credentialstorage.secret.Credential; +import com.microsoft.a4o.credentialstorage.secret.Token; +import com.microsoft.a4o.credentialstorage.secret.TokenPair; +import com.microsoft.a4o.credentialstorage.secret.TokenType; import java.io.BufferedReader; import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.PrintWriter; import java.io.StringReader; import java.util.HashMap; import java.util.Map; -import java.util.concurrent.atomic.AtomicReference; +import java.util.function.Function; import java.util.regex.Matcher; import java.util.regex.Pattern; class KeychainSecurityCliStore { - static final String SECURITY = "/usr/bin/security"; - static final String DELETE_GENERIC_PASSWORD = "delete-generic-password"; - static final String FIND_GENERIC_PASSWORD = "find-generic-password"; - static final String ADD_GENERIC_PASSWORD = "add-generic-password"; - static final String SHOW_KEYCHAIN_INFO = "show-keychain-info"; - static final String ACCOUNT_PARAMETER = "-a"; - static final String ACCOUNT_METADATA = "acct"; - static final String PASSWORD = "password"; + private static final String SECURITY = "/usr/bin/security"; + private static final String DELETE_GENERIC_PASSWORD = "delete-generic-password"; + private static final String FIND_GENERIC_PASSWORD = "find-generic-password"; + private static final String ADD_GENERIC_PASSWORD = "add-generic-password"; + private static final String ACCOUNT_PARAMETER = "-a"; + private static final String ACCOUNT_METADATA = "acct"; + private static final String PASSWORD = "password"; private static final String SERVICE_PARAMETER = "-s"; private static final String KIND_PARAMETER = "-D"; private static final String PASSWORD_PARAMETER = "-w"; @@ -42,64 +38,66 @@ class KeychainSecurityCliStore { private static final int USER_INTERACTION_NOT_ALLOWED_EXIT_CODE = 36; private static final String INTERACTIVE_MODE = "-i"; + private static final Function QUOTING_PROCESSOR = str -> str.contains(" ") ? '"' + str + '"' : str; + + private static final Pattern MetadataLinePattern = Pattern.compile + ( + // ^(\w+):\s"(.+)" + "^(\\w+):\\s\"(.+)\"" + ); + + enum SecretKind { + Credential, + Token, + TokenPair_Access_Token, + TokenPair_Refresh_Token + } + + enum AttributeParsingState { + Spaces, + StringKey, + HexKey, + BeforeType, + Type, + AfterType, + BeforeValue, + NullValue, + StringValue, + TimeDateValue, + ValueFinished + } + protected boolean deleteByKind(final String targetName, final SecretKind kind) { try { - final TestableProcess process = processFactory.create( + final ProcessBuilder processBuilder = new ProcessBuilder( SECURITY, DELETE_GENERIC_PASSWORD, SERVICE_PARAMETER, targetName, KIND_PARAMETER, kind.name() ); + + final Process process = processBuilder.start(); + // we don't care about the exit code process.waitFor(); return true; - } catch (final IOException e) { - throw new Error(e); - } catch (final InterruptedException e) { + } catch (final IOException | InterruptedException e) { throw new Error(e); } } - private static final Func QUOTING_PROCESSOR = new Func() { - @Override - public String call(final String s) { - if (s.contains(" ")) { - return '"' + s + '"'; - } - return s; - } - }; - - enum SecretKind { - Credential, - Token, - TokenPair_Access_Token, - TokenPair_Refresh_Token; - } - - private final TestableProcessFactory processFactory; - - public KeychainSecurityCliStore() { - this(new DefaultProcessFactory()); - } - - KeychainSecurityCliStore(final TestableProcessFactory processFactory) { - this.processFactory = processFactory; - } - - static Map parseKeychainMetaData(final String metadata) { - final Map result = new HashMap(); + private static Map parseKeychainMetaData(final String metadata) { + final Map result = new HashMap<>(); parseKeychainMetaData(metadata, result); return result; } - static void parseKeychainMetaData(final String metadata, final Map result) { + private static void parseKeychainMetaData(final String metadata, final Map result) { final StringReader sr = new StringReader(metadata); - final BufferedReader br = new BufferedReader(sr); - boolean parsingAttributes = false; - String line; - try { + try (BufferedReader br = new BufferedReader(sr)) { + boolean parsingAttributes = false; + String line; while ((line = br.readLine()) != null) { if (parsingAttributes) { parseAttributeLine(line, result); @@ -113,18 +111,10 @@ static void parseKeychainMetaData(final String metadata, final Map destination) { + private static void parseMetadataLine(final String line, final Map destination) { final Matcher matcher = MetadataLinePattern.matcher(line); if (matcher.matches()) { final String key = matcher.group(1); @@ -133,21 +123,7 @@ static void parseMetadataLine(final String line, final Map desti } } - enum AttributeParsingState { - Spaces, - StringKey, - HexKey, - BeforeType, - Type, - AfterType, - BeforeValue, - NullValue, - StringValue, - TimeDateValue, - ValueFinished,; - } - - static void parseAttributeLine(final String line, final Map destination) { + private static void parseAttributeLine(final String line, final Map destination) { final String template = "Undefined transition '%1$s' from %2$s."; final StringBuilder key = new StringBuilder(); final StringBuilder type = new StringBuilder(); @@ -200,41 +176,31 @@ static void parseAttributeLine(final String line, final Map dest } break; case StringKey: - switch (c) { - case '"': - state = AttributeParsingState.BeforeType; - break; - default: - key.append(c); - break; + if (c == '"') { + state = AttributeParsingState.BeforeType; + } else { + key.append(c); } break; case BeforeType: - switch (c) { - case '<': - state = AttributeParsingState.Type; - break; - default: - throw new Error(String.format(template, c, state)); + if (c == '<') { + state = AttributeParsingState.Type; + } else { + throw new Error(String.format(template, c, state)); } break; case Type: - switch (c) { - case '>': - state = AttributeParsingState.AfterType; - break; - default: - type.append(c); - break; + if (c == '>') { + state = AttributeParsingState.AfterType; + } else { + type.append(c); } break; case AfterType: - switch (c) { - case '=': - state = AttributeParsingState.BeforeValue; - break; - default: - throw new Error(String.format(template, c, state)); + if (c == '=') { + state = AttributeParsingState.BeforeValue; + } else { + throw new Error(String.format(template, c, state)); } break; case BeforeValue: @@ -295,31 +261,7 @@ static void parseAttributeLine(final String line, final Map dest // TODO: else if ("sint32".equals(type)) } - public boolean isKeychainAvailable() { - final String stdOut, stdErr; - try { - final TestableProcess process = processFactory.create( - SECURITY, - SHOW_KEYCHAIN_INFO - ); - final ProcessCoordinator coordinator = new ProcessCoordinator(process); - final int result = coordinator.waitFor(); - stdOut = coordinator.getStdOut(); - stdErr = coordinator.getStdErr(); - checkResult(result, stdOut, stdErr); - } catch (final IOException e) { - throw new Error(e); - } catch (final InterruptedException e) { - throw new Error(e); - } catch (final SecurityException e) { - return false; - } - return true; - } - - - - static void checkResult(final int result, final String stdOut, final String stdErr) { + private static void checkResult(final int result, final String stdOut, final String stdErr) { if (result != 0) { if (result == USER_INTERACTION_NOT_ALLOWED_EXIT_CODE) { throw new SecurityException("User interaction is not allowed."); @@ -331,26 +273,26 @@ static void checkResult(final int result, final String stdOut, final String stdE } } - static Map read(final SecretKind secretKind, final TestableProcessFactory processFactory, final String serviceName) { + private static Map read(final SecretKind secretKind, final String serviceName) { final String stdOut, stdErr; try { - final TestableProcess process = processFactory.create( + final ProcessBuilder processBuilder = new ProcessBuilder( SECURITY, FIND_GENERIC_PASSWORD, SERVICE_PARAMETER, serviceName, KIND_PARAMETER, secretKind.name(), "-g" // "Display the password for the item found" ); - final ProcessCoordinator coordinator = new ProcessCoordinator(process); - final int result = coordinator.waitFor(); - stdOut = coordinator.getStdOut(); - stdErr = coordinator.getStdErr(); + + final Process process = processBuilder.start(); + + final int result = process.waitFor(); + stdOut = readToString(process.getInputStream()); + stdErr = readToString(process.getErrorStream()); if (result != 0 && result != ITEM_NOT_FOUND_EXIT_CODE) { checkResult(result, stdOut, stdErr); } - } catch (final IOException e) { - throw new Error(e); - } catch (final InterruptedException e) { + } catch (final IOException | InterruptedException e) { throw new Error(e); } @@ -361,7 +303,7 @@ static Map read(final SecretKind secretKind, final TestableProce } public Credential readCredentials(final String targetName) { - final Map metaData = read(SecretKind.Credential, processFactory, targetName); + final Map metaData = read(SecretKind.Credential, targetName); final Credential result; if (metaData.size() > 0) { @@ -377,14 +319,14 @@ public Credential readCredentials(final String targetName) { } public Token readToken(final String targetName) { - final Map metaData = read(SecretKind.Token, processFactory, targetName); + final Map metaData = read(SecretKind.Token, targetName); final Token result; if (metaData.size() > 0) { final String typeName = (String) metaData.get(ACCOUNT_METADATA); final String password = (String) metaData.get(PASSWORD); - result = new Token(password, typeName); + result = new Token(password, TokenType.valueOf(typeName)); } else { result = null; } @@ -395,20 +337,18 @@ public Token readToken(final String targetName) { public TokenPair readTokenPair(final String targetName) { String accessToken, refreshToken; - final Map accessTokenMetaData = read(SecretKind.TokenPair_Access_Token, processFactory, targetName); + final Map accessTokenMetaData = read(SecretKind.TokenPair_Access_Token, targetName); if (accessTokenMetaData.size() > 0) { - final String password = (String) accessTokenMetaData.get(PASSWORD); - accessToken = password; + accessToken = (String) accessTokenMetaData.get(PASSWORD); } else { accessToken = null; } - final Map refreshTokenMetaData = read(SecretKind.TokenPair_Refresh_Token, processFactory, targetName); + final Map refreshTokenMetaData = read(SecretKind.TokenPair_Refresh_Token, targetName); if (refreshTokenMetaData.size() > 0) { - final String password = (String) refreshTokenMetaData.get(PASSWORD); - refreshToken = password; + refreshToken = (String) refreshTokenMetaData.get(PASSWORD); } else { refreshToken = null; } @@ -420,10 +360,10 @@ public TokenPair readTokenPair(final String targetName) { return null; } - static void write(final SecretKind secretKind, final TestableProcessFactory processFactory, final String serviceName, final String accountName, final String password) { + private static void write(final SecretKind secretKind, final String serviceName, final String accountName, final String password) { final String stdOut, stdErr; try { - final TestableProcess addProcess = processFactory.create( + final ProcessBuilder addProcessBuilder = new ProcessBuilder( SECURITY, INTERACTIVE_MODE ); @@ -435,22 +375,24 @@ static void write(final SecretKind secretKind, final TestableProcessFactory proc PASSWORD_PARAMETER, password, KIND_PARAMETER, secretKind.name() }; - final ProcessCoordinator coordinator = new ProcessCoordinator(addProcess); + final Process process = addProcessBuilder.start(); final String command = StringHelper.join(" ", commandParts, 0, commandParts.length, QUOTING_PROCESSOR); - coordinator.println(command); - final int result = coordinator.waitFor(); - stdOut = coordinator.getStdOut(); - stdErr = coordinator.getStdErr(); + + try (final PrintWriter writer = new PrintWriter(process.getOutputStream())) { + writer.println(command); + } + + final int result = process.waitFor(); + stdOut = readToString(process.getInputStream()); + stdErr = readToString(process.getErrorStream()); checkResult(result, stdOut, stdErr); - } catch (final IOException e) { - throw new Error(e); - } catch (final InterruptedException e) { + } catch (final IOException | InterruptedException e) { throw new Error(e); } } public void writeCredential(final String targetName, final Credential credentials) { - write(SecretKind.Credential, processFactory, targetName, credentials.Username, credentials.Password); + write(SecretKind.Credential, targetName, credentials.getUsername(), credentials.getPassword()); } public void writeToken(final String targetName, final Token token) { @@ -458,19 +400,30 @@ public void writeToken(final String targetName, final Token token) { } private void writeTokenKind(final String targetName, final SecretKind secretKind, final Token token) { - final AtomicReference accountNameReference = new AtomicReference(); - Token.getFriendlyNameFromType(token.Type, accountNameReference); - final String accountName = accountNameReference.get(); - write(secretKind, processFactory, targetName, accountName, token.Value); + final String accountName = token.getType().name(); + write(secretKind, targetName, accountName, token.getValue()); } public void writeTokenPair(final String targetName, final TokenPair tokenPair) { - if (tokenPair.AccessToken.Value != null) { - writeTokenKind(targetName, SecretKind.TokenPair_Access_Token, tokenPair.AccessToken); + if (tokenPair.getAccessToken().getValue() != null) { + writeTokenKind(targetName, SecretKind.TokenPair_Access_Token, tokenPair.getAccessToken()); + } + + if (tokenPair.getRefreshToken().getValue() != null) { + writeTokenKind(targetName, SecretKind.TokenPair_Refresh_Token, tokenPair.getRefreshToken()); } + } - if (tokenPair.RefreshToken.Value != null) { - writeTokenKind(targetName, SecretKind.TokenPair_Refresh_Token, tokenPair.RefreshToken); + private static String readToString(final InputStream stream) throws IOException { + try (BufferedReader reader = new BufferedReader(new InputStreamReader(stream))) { + final StringBuilder sb = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + sb.append(line); + sb.append(System.getProperty("line.separator")); + } + return sb.toString(); } } + } diff --git a/src/main/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedCredentialStore.java b/src/main/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedCredentialStore.java new file mode 100644 index 00000000..793a12f9 --- /dev/null +++ b/src/main/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedCredentialStore.java @@ -0,0 +1,124 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root. + +package com.microsoft.a4o.credentialstorage.storage.posix; + +import com.microsoft.a4o.credentialstorage.helpers.StringHelper; +import com.microsoft.a4o.credentialstorage.helpers.XmlHelper; +import com.microsoft.a4o.credentialstorage.secret.Credential; +import com.microsoft.a4o.credentialstorage.storage.posix.internal.GnomeKeyringBackedSecureStore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.Text; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Objects; + +public class GnomeKeyringBackedCredentialStore extends GnomeKeyringBackedSecureStore { + + private static final Logger logger = LoggerFactory.getLogger(GnomeKeyringBackedCredentialStore.class); + + @Override + protected String getType() { + return "Credential"; + } + + @Override + protected String serialize(final Credential credential) { + Objects.requireNonNull(credential, "Credential cannot be null"); + + return toXmlString(credential); + } + + @Override + protected Credential deserialize(final String secret) { + Objects.requireNonNull(secret, "secret cannot be null"); + + try { + return fromXmlString(secret); + } catch (final Exception e) { + logger.error("Failed to deserialize credential.", e); + return null; + } + } + + private static Credential fromXmlString(final String xmlString) { + final byte[] bytes = StringHelper.UTF8GetBytes(xmlString); + final ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); + return fromXmlStream(inputStream); + } + + private static Credential fromXmlStream(final InputStream source) { + final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + try { + final DocumentBuilder builder = dbf.newDocumentBuilder(); + final Document document = builder.parse(source); + final Element rootElement = document.getDocumentElement(); + + return fromXml(rootElement); + } + catch (final Exception e) { + throw new Error(e); + } + } + + private static String toXmlString(final Credential credential) { + final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + try { + final DocumentBuilder builder = dbf.newDocumentBuilder(); + final Document document = builder.newDocument(); + + final Element element = toXml(credential, document); + document.appendChild(element); + + return XmlHelper.toString(document); + } + catch (final Exception e) { + throw new Error(e); + } + } + + static Credential fromXml(final Node credentialNode) { + Credential value; + String password = null; + String username = null; + + final NodeList propertyNodes = credentialNode.getChildNodes(); + for (int v = 0; v < propertyNodes.getLength(); v++) { + final Node propertyNode = propertyNodes.item(v); + if (propertyNode.getNodeType() != Node.ELEMENT_NODE) continue; + + final String propertyName = propertyNode.getNodeName(); + if ("Password".equals(propertyName)) { + password = XmlHelper.getText(propertyNode); + } else if ("Username".equals(propertyName)) { + username = XmlHelper.getText(propertyNode); + } + } + value = new Credential(username, password); + return value; + } + + static Element toXml(final Credential credential, final Document document) { + final Element valueNode = document.createElement("value"); + + final Element passwordNode = document.createElement("Password"); + final Text passwordValue = document.createTextNode(credential.getPassword()); + passwordNode.appendChild(passwordValue); + valueNode.appendChild(passwordNode); + + final Element usernameNode = document.createElement("Username"); + final Text usernameValue = document.createTextNode(credential.getUsername()); + usernameNode.appendChild(usernameValue); + valueNode.appendChild(usernameNode); + + return valueNode; + } +} diff --git a/src/main/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedTokenPairStore.java b/src/main/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedTokenPairStore.java new file mode 100644 index 00000000..0bc46116 --- /dev/null +++ b/src/main/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedTokenPairStore.java @@ -0,0 +1,124 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root. + +package com.microsoft.a4o.credentialstorage.storage.posix; + +import com.microsoft.a4o.credentialstorage.helpers.StringHelper; +import com.microsoft.a4o.credentialstorage.helpers.XmlHelper; +import com.microsoft.a4o.credentialstorage.secret.TokenPair; +import com.microsoft.a4o.credentialstorage.storage.posix.internal.GnomeKeyringBackedSecureStore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NodeList; +import org.w3c.dom.Text; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.util.Objects; + +public class GnomeKeyringBackedTokenPairStore extends GnomeKeyringBackedSecureStore { + + private static final Logger logger = LoggerFactory.getLogger(GnomeKeyringBackedTokenPairStore.class); + + @Override + protected String serialize(final TokenPair tokenPair) { + Objects.requireNonNull(tokenPair, "TokenPair cannot be null"); + + return toXmlString(tokenPair); + } + + @Override + protected TokenPair deserialize(final String secret) { + Objects.requireNonNull(secret, "secret cannot be null"); + + try { + return fromXmlString(secret); + } catch (final Exception e) { + logger.error("Failed to deserialize the stored secret. Return null.", e); + return null; + } + } + + @Override + protected String getType() { + return "OAuth2Token"; + } + + static TokenPair fromXml(final Node tokenPairNode) { + TokenPair value; + + String accessToken = null; + String refreshToken = null; + + final NodeList propertyNodes = tokenPairNode.getChildNodes(); + for (int v = 0; v < propertyNodes.getLength(); v++) { + final Node propertyNode = propertyNodes.item(v); + final String propertyName = propertyNode.getNodeName(); + if ("accessToken".equals(propertyName)) { + accessToken = XmlHelper.getText(propertyNode); + } else if ("refreshToken".equals(propertyName)) { + refreshToken = XmlHelper.getText(propertyNode); + } + } + + value = new TokenPair(accessToken, refreshToken); + return value; + } + + static Element toXml(final TokenPair tokenPair, final Document document) { + final Element valueNode = document.createElement("value"); + + final Element accessTokenNode = document.createElement("accessToken"); + final Text accessTokenValue = document.createTextNode(tokenPair.getAccessToken().getValue()); + accessTokenNode.appendChild(accessTokenValue); + valueNode.appendChild(accessTokenNode); + + final Element refreshTokenNode = document.createElement("refreshToken"); + final Text refreshTokenValue = document.createTextNode(tokenPair.getRefreshToken().getValue()); + refreshTokenNode.appendChild(refreshTokenValue); + valueNode.appendChild(refreshTokenNode); + + return valueNode; + } + + private static String toXmlString(final TokenPair tokenPair) { + final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + try { + final DocumentBuilder builder = dbf.newDocumentBuilder(); + final Document document = builder.newDocument(); + + final Element element = toXml(tokenPair, document); + document.appendChild(element); + + return XmlHelper.toString(document); + } + catch (final Exception e) { + throw new Error(e); + } + } + + private static TokenPair fromXmlString(final String xmlString) { + final byte[] bytes = StringHelper.UTF8GetBytes(xmlString); + final ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); + return fromXmlStream(inputStream); + } + + private static TokenPair fromXmlStream(final InputStream source) { + final DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); + try { + final DocumentBuilder builder = documentBuilderFactory.newDocumentBuilder(); + final Document document = builder.parse(source); + final Element rootElement = document.getDocumentElement(); + + return fromXml(rootElement); + } + catch (final Exception e) { + throw new Error(e); + } + } +} diff --git a/storage/src/main/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedTokenStore.java b/src/main/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedTokenStore.java similarity index 62% rename from storage/src/main/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedTokenStore.java rename to src/main/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedTokenStore.java index 90debb50..43e806bf 100644 --- a/storage/src/main/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedTokenStore.java +++ b/src/main/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedTokenStore.java @@ -1,11 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.posix; +package com.microsoft.a4o.credentialstorage.storage.posix; -import com.microsoft.alm.secret.Token; -import com.microsoft.alm.secret.TokenType; -import com.microsoft.alm.storage.posix.internal.GnomeKeyringBackedSecureStore; +import com.microsoft.a4o.credentialstorage.secret.Token; +import com.microsoft.a4o.credentialstorage.secret.TokenType; +import com.microsoft.a4o.credentialstorage.storage.posix.internal.GnomeKeyringBackedSecureStore; public class GnomeKeyringBackedTokenStore extends GnomeKeyringBackedSecureStore { @@ -16,7 +16,7 @@ protected Token deserialize(final String secret) { @Override protected String serialize(final Token secret) { - return secret.Value; + return secret.getValue(); } @Override diff --git a/storage/src/main/java/com/microsoft/alm/storage/posix/internal/GLibInitializer.java b/src/main/java/com/microsoft/a4o/credentialstorage/storage/posix/internal/GLibInitializer.java similarity index 78% rename from storage/src/main/java/com/microsoft/alm/storage/posix/internal/GLibInitializer.java rename to src/main/java/com/microsoft/a4o/credentialstorage/storage/posix/internal/GLibInitializer.java index d91d7e18..e0c0a901 100644 --- a/storage/src/main/java/com/microsoft/alm/storage/posix/internal/GLibInitializer.java +++ b/src/main/java/com/microsoft/a4o/credentialstorage/storage/posix/internal/GLibInitializer.java @@ -1,12 +1,12 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.posix.internal; +package com.microsoft.a4o.credentialstorage.storage.posix.internal; /** - * Singleton instance to make sure we only initialize glib once. + * Singleton-instance to make sure we only initialize glib once. * - * Otherwise we may see warnings such as: g_set_application_name() called multiple times + * Otherwise, we may see warnings such as: g_set_application_name() called multiple times */ public class GLibInitializer { diff --git a/storage/src/main/java/com/microsoft/alm/storage/posix/internal/GLibLibrary.java b/src/main/java/com/microsoft/a4o/credentialstorage/storage/posix/internal/GLibLibrary.java similarity index 72% rename from storage/src/main/java/com/microsoft/alm/storage/posix/internal/GLibLibrary.java rename to src/main/java/com/microsoft/a4o/credentialstorage/storage/posix/internal/GLibLibrary.java index e97a4798..754d5e70 100644 --- a/storage/src/main/java/com/microsoft/alm/storage/posix/internal/GLibLibrary.java +++ b/src/main/java/com/microsoft/a4o/credentialstorage/storage/posix/internal/GLibLibrary.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.posix.internal; +package com.microsoft.a4o.credentialstorage.storage.posix.internal; import com.sun.jna.Library; import com.sun.jna.Native; @@ -11,8 +11,7 @@ * warning: "g_set_application_name not set" */ public interface GLibLibrary extends Library { - GLibLibrary INSTANCE = (GLibLibrary) - Native.loadLibrary("glib-2.0", GLibLibrary.class); + GLibLibrary INSTANCE = Native.load("glib-2.0", GLibLibrary.class); void g_set_application_name(final String application_name); } diff --git a/storage/src/main/java/com/microsoft/alm/storage/posix/internal/GnomeKeyringBackedSecureStore.java b/src/main/java/com/microsoft/a4o/credentialstorage/storage/posix/internal/GnomeKeyringBackedSecureStore.java similarity index 94% rename from storage/src/main/java/com/microsoft/alm/storage/posix/internal/GnomeKeyringBackedSecureStore.java rename to src/main/java/com/microsoft/a4o/credentialstorage/storage/posix/internal/GnomeKeyringBackedSecureStore.java index aadd38c7..f6e1aa03 100644 --- a/storage/src/main/java/com/microsoft/alm/storage/posix/internal/GnomeKeyringBackedSecureStore.java +++ b/src/main/java/com/microsoft/a4o/credentialstorage/storage/posix/internal/GnomeKeyringBackedSecureStore.java @@ -1,16 +1,16 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.posix.internal; +package com.microsoft.a4o.credentialstorage.storage.posix.internal; -import com.microsoft.alm.helpers.Debug; -import com.microsoft.alm.helpers.SettingsHelper; -import com.microsoft.alm.helpers.SystemHelper; -import com.microsoft.alm.secret.Secret; -import com.microsoft.alm.storage.SecretStore; +import com.microsoft.a4o.credentialstorage.helpers.SystemHelper; +import com.microsoft.a4o.credentialstorage.secret.Secret; +import com.microsoft.a4o.credentialstorage.storage.SecretStore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import java.util.Objects; + public abstract class GnomeKeyringBackedSecureStore implements SecretStore { private static final Logger logger = LoggerFactory.getLogger(GnomeKeyringBackedSecureStore.class); @@ -51,7 +51,7 @@ public abstract class GnomeKeyringBackedSecureStore implements */ @Override public E get(final String key) { - Debug.Assert(key != null, "key cannot be null"); + Objects.requireNonNull(key, "key cannot be null"); logger.info("Getting {} for {}", getType(), key); @@ -78,7 +78,7 @@ public E get(final String key) { @Override public boolean delete(final String key) { - Debug.Assert(key != null, "key cannot be null"); + Objects.requireNonNull(key, "key cannot be null"); logger.info("Deleting {} for {}", getType(), key); final int result = INSTANCE.gnome_keyring_delete_password_sync( @@ -92,8 +92,8 @@ public boolean delete(final String key) { @Override public boolean add(final String key, E secret) { - Debug.Assert(key != null, "key cannot be null"); - Debug.Assert(secret != null, "Secret cannot be null"); + Objects.requireNonNull(key, "key cannot be null"); + Objects.requireNonNull(secret, "Secret cannot be null"); logger.info("Adding a {} for {}", getType(), key); @@ -193,7 +193,7 @@ private static boolean isGnomeKeyringUnlocked(final GnomeKeyringLibrary.PointerT logger.info("Keyring is locked, most likely due to UI is unavailable or user logged in " + "automatically without supplying a password."); - final boolean allowUnlock = Boolean.valueOf(SettingsHelper.getInstance().getProperty(ALLOW_UNLOCK_KEYRING)); + final boolean allowUnlock = Boolean.parseBoolean(System.getProperty(ALLOW_UNLOCK_KEYRING)); if (allowUnlock) { final int ret = INSTANCE.gnome_keyring_unlock_sync(GnomeKeyringLibrary.GNOME_KEYRING_DEFAULT, null); return checkResult(ret, "Could not unlock keyring. GNOME Keyring is not available."); diff --git a/storage/src/main/java/com/microsoft/alm/storage/posix/internal/GnomeKeyringLibrary.java b/src/main/java/com/microsoft/a4o/credentialstorage/storage/posix/internal/GnomeKeyringLibrary.java similarity index 96% rename from storage/src/main/java/com/microsoft/alm/storage/posix/internal/GnomeKeyringLibrary.java rename to src/main/java/com/microsoft/a4o/credentialstorage/storage/posix/internal/GnomeKeyringLibrary.java index fa480e7a..503a704b 100644 --- a/storage/src/main/java/com/microsoft/alm/storage/posix/internal/GnomeKeyringLibrary.java +++ b/src/main/java/com/microsoft/a4o/credentialstorage/storage/posix/internal/GnomeKeyringLibrary.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.posix.internal; +package com.microsoft.a4o.credentialstorage.storage.posix.internal; import com.sun.jna.Library; import com.sun.jna.Native; @@ -19,8 +19,7 @@ */ public interface GnomeKeyringLibrary extends Library { - GnomeKeyringLibrary INSTANCE = (GnomeKeyringLibrary) - Native.loadLibrary("gnome-keyring", GnomeKeyringLibrary.class); + GnomeKeyringLibrary INSTANCE = Native.load("gnome-keyring", GnomeKeyringLibrary.class); /** * Save secrets to disk @@ -92,7 +91,7 @@ public interface GnomeKeyringLibrary extends Library { class GnomeKeyringPasswordSchemaAttribute extends Structure { @Override - protected List getFieldOrder() { + protected List getFieldOrder() { return Arrays.asList("name", "type"); } @@ -109,7 +108,7 @@ protected List getFieldOrder() { class GnomeKeyringPasswordSchema extends Structure { @Override - protected List getFieldOrder() { + protected List getFieldOrder() { return Arrays.asList("item_type", "attributes"); } @@ -124,7 +123,7 @@ protected List getFieldOrder() { class PointerToPointer extends Structure { @Override - protected List getFieldOrder() { + protected List getFieldOrder() { return Collections.singletonList("pointer"); } diff --git a/storage/src/main/java/com/microsoft/alm/storage/windows/CredManagerBackedCredentialStore.java b/src/main/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedCredentialStore.java similarity index 64% rename from storage/src/main/java/com/microsoft/alm/storage/windows/CredManagerBackedCredentialStore.java rename to src/main/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedCredentialStore.java index d30d437c..fe228d11 100644 --- a/storage/src/main/java/com/microsoft/alm/storage/windows/CredManagerBackedCredentialStore.java +++ b/src/main/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedCredentialStore.java @@ -1,10 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.windows; +package com.microsoft.a4o.credentialstorage.storage.windows; -import com.microsoft.alm.secret.Credential; -import com.microsoft.alm.storage.windows.internal.CredManagerBackedSecureStore; +import com.microsoft.a4o.credentialstorage.secret.Credential; +import com.microsoft.a4o.credentialstorage.storage.windows.internal.CredManagerBackedSecureStore; public class CredManagerBackedCredentialStore extends CredManagerBackedSecureStore { @@ -15,11 +15,11 @@ protected Credential create(final String username, final String secret) { @Override protected String getUsername(final Credential cred) { - return cred.Username; + return cred.getUsername(); } @Override protected String getCredentialBlob(final Credential cred) { - return cred.Password; + return cred.getPassword(); } } diff --git a/storage/src/main/java/com/microsoft/alm/storage/windows/CredManagerBackedTokenPairStore.java b/src/main/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedTokenPairStore.java similarity index 62% rename from storage/src/main/java/com/microsoft/alm/storage/windows/CredManagerBackedTokenPairStore.java rename to src/main/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedTokenPairStore.java index ab5eefb7..7a2af7af 100644 --- a/storage/src/main/java/com/microsoft/alm/storage/windows/CredManagerBackedTokenPairStore.java +++ b/src/main/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedTokenPairStore.java @@ -1,17 +1,18 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.windows; +package com.microsoft.a4o.credentialstorage.storage.windows; -import com.microsoft.alm.secret.TokenPair; -import com.microsoft.alm.storage.windows.internal.CredManagerBackedSecureStore; +import com.microsoft.a4o.credentialstorage.secret.TokenPair; +import com.microsoft.a4o.credentialstorage.storage.windows.internal.CredManagerBackedSecureStore; public class CredManagerBackedTokenPairStore extends CredManagerBackedSecureStore { - public static final String TOKEN_PAIR_USERNAME = "Azure Active Directory Access and Refresh Token"; + public static final String TOKEN_PAIR_USERNAME = "Access and Refresh Token"; + @Override protected TokenPair create(final String username, final String secret) { - return new TokenPair("", secret); + return new TokenPair(username, secret); } @Override @@ -23,6 +24,6 @@ protected String getUsername(final TokenPair tokenPair) { protected String getCredentialBlob(final TokenPair tokenPair) { // Only save refresh token on Windows // Cred Manager has a 4K size limit, need to make sure we stay under this limit - return tokenPair.RefreshToken.Value; + return tokenPair.getRefreshToken().getValue(); } } diff --git a/storage/src/main/java/com/microsoft/alm/storage/windows/CredManagerBackedTokenStore.java b/src/main/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedTokenStore.java similarity index 66% rename from storage/src/main/java/com/microsoft/alm/storage/windows/CredManagerBackedTokenStore.java rename to src/main/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedTokenStore.java index ee21f6d0..62316012 100644 --- a/storage/src/main/java/com/microsoft/alm/storage/windows/CredManagerBackedTokenStore.java +++ b/src/main/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedTokenStore.java @@ -1,11 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.windows; +package com.microsoft.a4o.credentialstorage.storage.windows; -import com.microsoft.alm.secret.Token; -import com.microsoft.alm.secret.TokenType; -import com.microsoft.alm.storage.windows.internal.CredManagerBackedSecureStore; +import com.microsoft.a4o.credentialstorage.secret.Token; +import com.microsoft.a4o.credentialstorage.secret.TokenType; +import com.microsoft.a4o.credentialstorage.storage.windows.internal.CredManagerBackedSecureStore; public class CredManagerBackedTokenStore extends CredManagerBackedSecureStore { @@ -23,6 +23,6 @@ protected String getUsername(final Token token) { @Override protected String getCredentialBlob(final Token token) { - return token.Value; + return token.getValue(); } } diff --git a/storage/src/main/java/com/microsoft/alm/storage/windows/internal/CredAdvapi32.java b/src/main/java/com/microsoft/a4o/credentialstorage/storage/windows/internal/CredAdvapi32.java similarity index 97% rename from storage/src/main/java/com/microsoft/alm/storage/windows/internal/CredAdvapi32.java rename to src/main/java/com/microsoft/a4o/credentialstorage/storage/windows/internal/CredAdvapi32.java index 048e7c78..1f877289 100644 --- a/storage/src/main/java/com/microsoft/alm/storage/windows/internal/CredAdvapi32.java +++ b/src/main/java/com/microsoft/a4o/credentialstorage/storage/windows/internal/CredAdvapi32.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.windows.internal; +package com.microsoft.a4o.credentialstorage.storage.windows.internal; import com.sun.jna.LastErrorException; import com.sun.jna.Memory; @@ -23,8 +23,7 @@ * Please refer to MSDN documentations for each method usage pattern */ interface CredAdvapi32 extends StdCallLibrary { - CredAdvapi32 INSTANCE = (CredAdvapi32) Native.loadLibrary("Advapi32", - CredAdvapi32.class, W32APIOptions.UNICODE_OPTIONS); + CredAdvapi32 INSTANCE = Native.load("Advapi32", CredAdvapi32.class, W32APIOptions.UNICODE_OPTIONS); /** * CredRead flag */ @@ -74,7 +73,7 @@ class CREDENTIAL_ATTRIBUTE extends Structure { public static class ByReference extends CREDENTIAL_ATTRIBUTE implements Structure.ByReference { } @Override - protected List getFieldOrder() { + protected List getFieldOrder() { return Arrays.asList("Keyword", "Flags", "ValueSize", @@ -110,12 +109,12 @@ protected List getFieldOrder() { } /** - * Pointer to {@See CREDENTIAL_ATTRIBUTE} struct + * Pointer to {@see CREDENTIAL_ATTRIBUTE} struct */ class PCREDENTIAL_ATTRIBUTE extends Structure { @Override - protected List getFieldOrder() { + protected List getFieldOrder() { return Collections.singletonList("credential_attribute"); } @@ -297,7 +296,7 @@ public CREDENTIAL(Pointer memory) { class PCREDENTIAL extends Structure { @Override - protected List getFieldOrder() { + protected List getFieldOrder() { return Collections.singletonList("credential"); } diff --git a/storage/src/main/java/com/microsoft/alm/storage/windows/internal/CredManagerBackedSecureStore.java b/src/main/java/com/microsoft/a4o/credentialstorage/storage/windows/internal/CredManagerBackedSecureStore.java similarity index 89% rename from storage/src/main/java/com/microsoft/alm/storage/windows/internal/CredManagerBackedSecureStore.java rename to src/main/java/com/microsoft/a4o/credentialstorage/storage/windows/internal/CredManagerBackedSecureStore.java index ea31537e..e1b84870 100644 --- a/storage/src/main/java/com/microsoft/alm/storage/windows/internal/CredManagerBackedSecureStore.java +++ b/src/main/java/com/microsoft/a4o/credentialstorage/storage/windows/internal/CredManagerBackedSecureStore.java @@ -1,13 +1,12 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.windows.internal; +package com.microsoft.a4o.credentialstorage.storage.windows.internal; -import com.microsoft.alm.helpers.Debug; -import com.microsoft.alm.helpers.StringHelper; -import com.microsoft.alm.helpers.SystemHelper; -import com.microsoft.alm.secret.Secret; -import com.microsoft.alm.storage.SecretStore; +import com.microsoft.a4o.credentialstorage.helpers.StringHelper; +import com.microsoft.a4o.credentialstorage.helpers.SystemHelper; +import com.microsoft.a4o.credentialstorage.secret.Secret; +import com.microsoft.a4o.credentialstorage.storage.SecretStore; import com.sun.jna.LastErrorException; import com.sun.jna.Memory; import com.sun.jna.Pointer; @@ -15,8 +14,7 @@ import org.slf4j.LoggerFactory; import java.util.Arrays; - -import static com.microsoft.alm.helpers.LoggingHelper.logError; +import java.util.Objects; /** * This class exposes functions to interact with Windows Credential Manager @@ -69,12 +67,12 @@ public abstract class CredManagerBackedSecureStore implements */ @Override public E get(String key) { - Debug.Assert(key != null, "key cannot be null"); + Objects.requireNonNull(key, "key cannot be null"); logger.info("Getting secret for {}", key); final CredAdvapi32.PCREDENTIAL pcredential = new CredAdvapi32.PCREDENTIAL(); - boolean read = false; + boolean read; E cred; try { @@ -97,7 +95,7 @@ public E get(String key) { } } catch (final LastErrorException e) { - logError(logger, "Getting secret failed.", e); + logger.error("Getting secret failed.", e); cred = null; } finally { @@ -124,7 +122,7 @@ public E get(String key) { */ @Override public boolean delete(String key) { - Debug.Assert(key != null, "key cannot be null"); + Objects.requireNonNull(key, "key cannot be null"); logger.info("Deleting secret for {}", key); @@ -133,7 +131,7 @@ public boolean delete(String key) { return INSTANCE.CredDelete(key, CredAdvapi32.CRED_TYPE_GENERIC, 0); } } catch (LastErrorException e) { - logError(logger, "Deleteing secret failed.", e); + logger.error("Deleting secret failed.", e); return false; } } @@ -152,8 +150,8 @@ public boolean delete(String key) { */ @Override public boolean add(String key, E secret) { - Debug.Assert(key != null, "key cannot be null"); - Debug.Assert(secret != null, "Secret cannot be null"); + Objects.requireNonNull(key, "key cannot be null"); + Objects.requireNonNull(secret, "Secret cannot be null"); logger.info("Adding secret for {}", key); @@ -171,7 +169,7 @@ public boolean add(String key, E secret) { return true; } catch (LastErrorException e) { - logError(logger, "Adding secret failed.", e); + logger.error("Adding secret failed.", e); return false; } finally { cred.CredentialBlob.clear(credBlob.length); diff --git a/common/src/test/java/com/microsoft/alm/helpers/StringHelperTest.java b/src/test/java/com/microsoft/a4o/credentialstorage/helpers/StringHelperTest.java similarity index 54% rename from common/src/test/java/com/microsoft/alm/helpers/StringHelperTest.java rename to src/test/java/com/microsoft/a4o/credentialstorage/helpers/StringHelperTest.java index 906a8ff0..c520aa10 100644 --- a/common/src/test/java/com/microsoft/alm/helpers/StringHelperTest.java +++ b/src/test/java/com/microsoft/a4o/credentialstorage/helpers/StringHelperTest.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.helpers; +package com.microsoft.a4o.credentialstorage.helpers; import org.junit.Assert; import org.junit.Test; @@ -9,32 +9,9 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; +import java.util.function.Function; public class StringHelperTest { - @Test - public void endsWithIgnoreCase_positive() { - Assert.assertTrue(StringHelper.endsWithIgnoreCase("Session", "Session")); - Assert.assertTrue(StringHelper.endsWithIgnoreCase("Session", "")); - Assert.assertTrue(StringHelper.endsWithIgnoreCase("Session", "SiOn")); - } - - @Test - public void endsWithIgnoreCase_negative() { - Assert.assertFalse(StringHelper.endsWithIgnoreCase("Session", "SiO")); - Assert.assertFalse(StringHelper.endsWithIgnoreCase("Session", "LongerThanSession")); - Assert.assertFalse(StringHelper.endsWithIgnoreCase("Session", "noisseS")); - } - - @Test(expected = IllegalArgumentException.class) - public void endsWithIgnoreCase_firstNull() { - Assert.assertTrue(StringHelper.endsWithIgnoreCase(null, "SiO")); - } - - @Test(expected = IllegalArgumentException.class) - public void endsWithIgnoreCase_secondNull() { - Assert.assertTrue(StringHelper.endsWithIgnoreCase("Session", null)); - } - @Test public void isNullOrWhiteSpace_null() { Assert.assertTrue(StringHelper.isNullOrWhiteSpace(null)); @@ -64,7 +41,7 @@ public void isNullOrWhiteSpace_content() { public void join_typical() { final String[] a = {"a", "b", "c"}; - final String actual = StringHelper.join(",", a, 0, a.length); + final String actual = StringHelper.join(",", a, 0, a.length, Function.identity()); Assert.assertEquals("a,b,c", actual); } @@ -73,7 +50,7 @@ public void join_typical() { public void join_edge_oneElementInArray() { final String[] a = {"a"}; - final String actual = StringHelper.join(",", a, 0, a.length); + final String actual = StringHelper.join(",", a, 0, a.length, Function.identity()); Assert.assertEquals("a", actual); } @@ -82,7 +59,7 @@ public void join_edge_oneElementInArray() { public void join_skipFirst() { final String[] a = {"a", "b", "c"}; - final String actual = StringHelper.join(",", a, 1, a.length - 1); + final String actual = StringHelper.join(",", a, 1, a.length - 1, Function.identity()); Assert.assertEquals("b,c", actual); } @@ -91,52 +68,35 @@ public void join_skipFirst() { public void join_skipLast() { final String[] a = {"a", "b", "c"}; - final String actual = StringHelper.join(",", a, 0, a.length - 1); + final String actual = StringHelper.join(",", a, 0, a.length - 1, Function.identity()); Assert.assertEquals("a,b", actual); } - @Test - public void join_typical_simpleOverload() { - final String[] a = {"a", "b", "c"}; - - final String actual = StringHelper.join(",", a); - - Assert.assertEquals("a,b,c", actual); - } - @Test public void join_returnsStringEmptyIfCountZero() { final String[] a = {"a", "b", "c"}; - Assert.assertEquals(StringHelper.Empty, StringHelper.join(",", a, 0, 0)); + Assert.assertEquals(StringHelper.Empty, StringHelper.join(",", a, 0, 0, Function.identity())); } @Test public void join_returnsStringEmptyIfValueHasNoElements() { final String[] emptyArray = {}; - Assert.assertEquals(StringHelper.Empty, StringHelper.join(",", emptyArray, 0, 0)); + Assert.assertEquals(StringHelper.Empty, StringHelper.join(",", emptyArray, 0, 0, Function.identity())); } @Test public void join_returnsStringEmptyIfSeparatorAndAllElementsAreEmpty() { final String[] arrayOfEmpty = {StringHelper.Empty, StringHelper.Empty, StringHelper.Empty}; - Assert.assertEquals(StringHelper.Empty, StringHelper.join(StringHelper.Empty, arrayOfEmpty, 0, 3)); + Assert.assertEquals(StringHelper.Empty, StringHelper.join(StringHelper.Empty, arrayOfEmpty, 0, 3, Function.identity())); } @Test public void join_withQuotingProcessor() { - final Func quotingProcessor = new Func() { - @Override - public String call(final String s) { - if (s.contains(" ")) { - return '"' + s + '"'; - } - return s; - } - }; + final Function quotingProcessor = str -> str.contains(" ") ? '"' + str + '"' : str; final String[] args = {"--user", "man-with-hat", "--password", "battery horse staple correct"}; final String actual = StringHelper.join(" ", args, 0, args.length, quotingProcessor); @@ -144,38 +104,6 @@ public String call(final String s) { Assert.assertEquals("--user man-with-hat --password \"battery horse staple correct\"", actual); } - @Test - public void trimEnd_documentationExample() { - final String actual = StringHelper.trimEnd("123abc456xyz789", '1', '2', '3', '4', '5', '6', '7', '8', '9'); - Assert.assertEquals("123abc456xyz", actual); - } - - @Test - public void trimEnd_edgeCases() { - Assert.assertEquals("", StringHelper.trimEnd("", ' ', '\t')); - Assert.assertEquals("", StringHelper.trimEnd(" ", ' ', '\t')); - Assert.assertEquals("a", StringHelper.trimEnd("a", ' ')); - Assert.assertEquals("a", StringHelper.trimEnd("a", ' ', '\t')); - Assert.assertEquals("a", StringHelper.trimEnd("a ", ' ')); - Assert.assertEquals("a", StringHelper.trimEnd("a ", ' ', '\t')); - Assert.assertEquals("a", StringHelper.trimEnd("a\t", ' ', '\t')); - Assert.assertEquals("a", StringHelper.trimEnd("a \t", ' ', '\t')); - Assert.assertEquals(" trimEnd \n", StringHelper.trimEnd(" trimEnd \n\t", ' ', '\t')); - Assert.assertEquals(" trimEnd", StringHelper.trimEnd(" trimEnd ", ' ', '\t')); - } - - @Test - public void trimEnd_defaultWhitespace() { - Assert.assertEquals("trimEnd", StringHelper.trimEnd("trimEnd")); - Assert.assertEquals("trimEnd", StringHelper.trimEnd("trimEnd ")); - Assert.assertEquals("trimEnd", StringHelper.trimEnd("trimEnd ", null)); - Assert.assertEquals("trimEnd", StringHelper.trimEnd("trimEnd ", new char[]{})); - Assert.assertEquals(" trimEnd", StringHelper.trimEnd(" trimEnd")); - Assert.assertEquals(" trimEnd", StringHelper.trimEnd(" trimEnd ")); - Assert.assertEquals("", StringHelper.trimEnd("")); - Assert.assertEquals("", StringHelper.trimEnd(" ")); - } - public static void assertLinesEqual(final String expected, final String actual) throws IOException { final StringReader expectedSr = new StringReader(expected); final BufferedReader expectedBr = new BufferedReader(expectedSr); diff --git a/common/src/test/java/com/microsoft/alm/helpers/XmlHelperTest.java b/src/test/java/com/microsoft/a4o/credentialstorage/helpers/XmlHelperTest.java similarity index 97% rename from common/src/test/java/com/microsoft/alm/helpers/XmlHelperTest.java rename to src/test/java/com/microsoft/a4o/credentialstorage/helpers/XmlHelperTest.java index 5a351f27..976e4867 100644 --- a/common/src/test/java/com/microsoft/alm/helpers/XmlHelperTest.java +++ b/src/test/java/com/microsoft/a4o/credentialstorage/helpers/XmlHelperTest.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.helpers; +package com.microsoft.a4o.credentialstorage.helpers; import org.junit.Assert; import org.junit.Test; diff --git a/storage/src/test/java/com/microsoft/alm/storage/StorageProviderTest.java b/src/test/java/com/microsoft/a4o/credentialstorage/storage/StorageProviderTest.java similarity index 79% rename from storage/src/test/java/com/microsoft/alm/storage/StorageProviderTest.java rename to src/test/java/com/microsoft/a4o/credentialstorage/storage/StorageProviderTest.java index 631dff94..55c74a9c 100644 --- a/storage/src/test/java/com/microsoft/alm/storage/StorageProviderTest.java +++ b/src/test/java/com/microsoft/a4o/credentialstorage/storage/StorageProviderTest.java @@ -1,23 +1,23 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage; +package com.microsoft.a4o.credentialstorage.storage; -import com.microsoft.alm.secret.Token; -import com.microsoft.alm.storage.StorageProvider.NonPersistentStoreGenerator; +import com.microsoft.a4o.credentialstorage.secret.Token; +import com.microsoft.a4o.credentialstorage.storage.SecretStore; +import com.microsoft.a4o.credentialstorage.storage.StorageProvider; import org.junit.Test; import java.util.ArrayList; import java.util.List; -import static com.microsoft.alm.storage.StorageProvider.SecureOption; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; public class StorageProviderTest { - private NonPersistentStoreGenerator generator = new NonPersistentStoreGenerator() { + private StorageProvider.NonPersistentStoreGenerator generator = new StorageProvider.NonPersistentStoreGenerator() { @Override public SecretStore getInsecureNonPersistentStore() { return getStore(false); @@ -35,7 +35,7 @@ public void withAvailableSecureStore_shouldReturnSecureStore() throws Exception //Add one persisted secure store to it candidates.add(getStore(true)); - final SecretStore actual = StorageProvider.getStore(true, SecureOption.MUST, candidates, generator); + final SecretStore actual = StorageProvider.getStore(true, StorageProvider.SecureOption.MUST, candidates, generator); assertTrue(actual.isSecure()); } @@ -43,7 +43,7 @@ public void withAvailableSecureStore_shouldReturnSecureStore() throws Exception public void noAvailableSecureStore_shouldReturnNull() throws Exception { List> candidates = new ArrayList>(); - final SecretStore actual = StorageProvider.getStore(true, SecureOption.MUST, candidates, generator); + final SecretStore actual = StorageProvider.getStore(true, StorageProvider.SecureOption.MUST, candidates, generator); assertNull(actual); } @@ -53,7 +53,7 @@ public void nonPersisted_MustBeSecure_shouldUseGenerator() throws Exception { //Add one persisted secure store to it candidates.add(getStore(true)); - final SecretStore actual = StorageProvider.getStore(false, SecureOption.MUST, candidates, generator); + final SecretStore actual = StorageProvider.getStore(false, StorageProvider.SecureOption.MUST, candidates, generator); assertNull(actual); } @@ -63,7 +63,7 @@ public void nonPersisted_doNotCareSecurity_shouldUseGenerator() throws Exception //Add one persisted secure store to it candidates.add(getStore(true)); - final SecretStore actual = StorageProvider.getStore(false, SecureOption.PREFER, candidates, generator); + final SecretStore actual = StorageProvider.getStore(false, StorageProvider.SecureOption.PREFER, candidates, generator); assertFalse(actual.isSecure()); } diff --git a/storage/src/test/java/com/microsoft/alm/storage/macosx/KeychainSecurityBackedCredentialStoreIT.java b/src/test/java/com/microsoft/a4o/credentialstorage/storage/macosx/KeychainSecurityBackedCredentialStoreIT.java similarity index 84% rename from storage/src/test/java/com/microsoft/alm/storage/macosx/KeychainSecurityBackedCredentialStoreIT.java rename to src/test/java/com/microsoft/a4o/credentialstorage/storage/macosx/KeychainSecurityBackedCredentialStoreIT.java index f6a2684f..dfa5ec52 100644 --- a/storage/src/test/java/com/microsoft/alm/storage/macosx/KeychainSecurityBackedCredentialStoreIT.java +++ b/src/test/java/com/microsoft/a4o/credentialstorage/storage/macosx/KeychainSecurityBackedCredentialStoreIT.java @@ -1,10 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.macosx; +package com.microsoft.a4o.credentialstorage.storage.macosx; -import com.microsoft.alm.helpers.SystemHelper; -import com.microsoft.alm.secret.Credential; +import com.microsoft.a4o.credentialstorage.helpers.SystemHelper; +import com.microsoft.a4o.credentialstorage.secret.Credential; import org.junit.Before; import org.junit.Test; @@ -36,8 +36,8 @@ public void e2eTestStoreReadDelete() { final Credential readCred = underTest.get(key); - assertEquals("Retrieved Credential.Username is different", username, credential.Username); - assertEquals("Retrieved Credential.Password is different", password, credential.Password); + assertEquals("Retrieved Credential.Username is different", username, credential.getUsername()); + assertEquals("Retrieved Credential.Password is different", password, credential.getPassword()); // The credential under the specified key should be deleted now underTest.delete(key); diff --git a/storage/src/test/java/com/microsoft/alm/storage/macosx/KeychainSecurityBackedTokenPairStoreIT.java b/src/test/java/com/microsoft/a4o/credentialstorage/storage/macosx/KeychainSecurityBackedTokenPairStoreIT.java similarity index 91% rename from storage/src/test/java/com/microsoft/alm/storage/macosx/KeychainSecurityBackedTokenPairStoreIT.java rename to src/test/java/com/microsoft/a4o/credentialstorage/storage/macosx/KeychainSecurityBackedTokenPairStoreIT.java index 2eb91569..28ff8141 100644 --- a/storage/src/test/java/com/microsoft/alm/storage/macosx/KeychainSecurityBackedTokenPairStoreIT.java +++ b/src/test/java/com/microsoft/a4o/credentialstorage/storage/macosx/KeychainSecurityBackedTokenPairStoreIT.java @@ -1,10 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.macosx; +package com.microsoft.a4o.credentialstorage.storage.macosx; -import com.microsoft.alm.helpers.SystemHelper; -import com.microsoft.alm.secret.TokenPair; +import com.microsoft.a4o.credentialstorage.helpers.SystemHelper; +import com.microsoft.a4o.credentialstorage.secret.TokenPair; import org.junit.Before; import org.junit.Test; @@ -37,8 +37,8 @@ public void e2eTestStoreReadDelete() { final TokenPair readToken = underTest.get(key); - assertEquals("Retrieved Access Token is different", sampleAccessToken, readToken.AccessToken.Value); - assertEquals("Retrieved Refresh Token is different", sampleRefreshToken, readToken.RefreshToken.Value); + assertEquals("Retrieved Access Token is different", sampleAccessToken, readToken.getAccessToken().getValue()); + assertEquals("Retrieved Refresh Token is different", sampleRefreshToken, readToken.getRefreshToken().getValue()); // The credential under the specified key should be deleted now underTest.delete(key); diff --git a/storage/src/test/java/com/microsoft/alm/storage/macosx/KeychainSecurityBackedTokenStoreIT.java b/src/test/java/com/microsoft/a4o/credentialstorage/storage/macosx/KeychainSecurityBackedTokenStoreIT.java similarity index 77% rename from storage/src/test/java/com/microsoft/alm/storage/macosx/KeychainSecurityBackedTokenStoreIT.java rename to src/test/java/com/microsoft/a4o/credentialstorage/storage/macosx/KeychainSecurityBackedTokenStoreIT.java index 0f734530..d9e39762 100644 --- a/storage/src/test/java/com/microsoft/alm/storage/macosx/KeychainSecurityBackedTokenStoreIT.java +++ b/src/test/java/com/microsoft/a4o/credentialstorage/storage/macosx/KeychainSecurityBackedTokenStoreIT.java @@ -1,11 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.macosx; +package com.microsoft.a4o.credentialstorage.storage.macosx; -import com.microsoft.alm.helpers.SystemHelper; -import com.microsoft.alm.secret.Token; -import com.microsoft.alm.secret.TokenType; +import com.microsoft.a4o.credentialstorage.helpers.SystemHelper; +import com.microsoft.a4o.credentialstorage.secret.Token; +import com.microsoft.a4o.credentialstorage.secret.TokenType; import org.junit.Before; import org.junit.Test; @@ -35,7 +35,7 @@ public void e2eTestStoreReadDelete() { final Token readToken = underTest.get(key); - assertEquals("Retrieved token is different", token.Value, readToken.Value); + assertEquals("Retrieved token is different", token.getValue(), readToken.getValue()); // The credential under the specified key should be deleted now underTest.delete(key); diff --git a/storage/src/test/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedCredentialStoreIT.java b/src/test/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedCredentialStoreIT.java similarity index 77% rename from storage/src/test/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedCredentialStoreIT.java rename to src/test/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedCredentialStoreIT.java index 8cd4a6d3..3d4e34ed 100644 --- a/storage/src/test/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedCredentialStoreIT.java +++ b/src/test/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedCredentialStoreIT.java @@ -1,10 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.posix; +package com.microsoft.a4o.credentialstorage.storage.posix; -import com.microsoft.alm.secret.Credential; -import com.microsoft.alm.storage.posix.internal.GnomeKeyringBackedSecureStore; +import com.microsoft.a4o.credentialstorage.secret.Credential; +import com.microsoft.a4o.credentialstorage.storage.posix.internal.GnomeKeyringBackedSecureStore; import org.junit.Before; import org.junit.Test; @@ -36,8 +36,8 @@ public void saveCredential() { final Credential readValue = underTest.get(testKey); - assertEquals(credential.Username, readValue.Username); - assertEquals(credential.Password, readValue.Password); + assertEquals(credential.getUsername(), readValue.getUsername()); + assertEquals(credential.getPassword(), readValue.getPassword()); boolean deleted = underTest.delete(testKey); assertTrue(deleted); diff --git a/src/test/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedCredentialStoreTest.java b/src/test/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedCredentialStoreTest.java new file mode 100644 index 00000000..db6f5832 --- /dev/null +++ b/src/test/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedCredentialStoreTest.java @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See License.txt in the project root. + +package com.microsoft.a4o.credentialstorage.storage.posix; + +import com.microsoft.a4o.credentialstorage.helpers.StringHelperTest; +import com.microsoft.a4o.credentialstorage.helpers.XmlHelper; +import com.microsoft.a4o.credentialstorage.secret.Credential; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.ByteArrayInputStream; + +import static org.junit.Assert.assertEquals; + +public class GnomeKeyringBackedCredentialStoreTest { + + GnomeKeyringBackedCredentialStore underTest; + + @Before + public void setUp() throws Exception { + underTest = new GnomeKeyringBackedCredentialStore(); + } + + @Test + public void serializeDeserialize_specialChars() { + final String username = "!@#$%^&*~"; + final String password = ":'\"/"; + final Credential cred = new Credential(username, password); + final String serialized = underTest.serialize(cred); + final Credential processedCred = underTest.deserialize(serialized); + assertEquals(username, processedCred.getUsername()); + assertEquals(password, processedCred.getPassword()); + } + + @Test + public void xmlSerialization_roundTrip() throws Exception { + final Credential credential = new Credential("douglas.adams", "42"); + final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + final DocumentBuilder builder = dbf.newDocumentBuilder(); + final Document serializationDoc = builder.newDocument(); + + final Element element = GnomeKeyringBackedCredentialStore.toXml(credential, serializationDoc); + + serializationDoc.appendChild(element); + final String actualXmlString = XmlHelper.toString(serializationDoc); + final String expectedXmlString = + "\n" + + "\n" + + " 42\n" + + " douglas.adams\n" + + ""; + StringHelperTest.assertLinesEqual(expectedXmlString, actualXmlString); + + final ByteArrayInputStream bais = new ByteArrayInputStream(actualXmlString.getBytes()); + final Document deserializationDoc = builder.parse(bais); + final Element rootNode = deserializationDoc.getDocumentElement(); + + final Credential actualCredential = GnomeKeyringBackedCredentialStore.fromXml(rootNode); + + Assert.assertEquals(credential.getUsername(), actualCredential.getUsername()); + Assert.assertEquals(credential.getPassword(), actualCredential.getPassword()); + } +} \ No newline at end of file diff --git a/storage/src/test/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedTokenPairStoreIT.java b/src/test/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedTokenPairStoreIT.java similarity index 90% rename from storage/src/test/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedTokenPairStoreIT.java rename to src/test/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedTokenPairStoreIT.java index c2fa8a7e..d2b1a860 100644 --- a/storage/src/test/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedTokenPairStoreIT.java +++ b/src/test/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedTokenPairStoreIT.java @@ -1,10 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.posix; +package com.microsoft.a4o.credentialstorage.storage.posix; -import com.microsoft.alm.secret.TokenPair; -import com.microsoft.alm.storage.posix.internal.GnomeKeyringBackedSecureStore; +import com.microsoft.a4o.credentialstorage.secret.TokenPair; +import com.microsoft.a4o.credentialstorage.storage.posix.internal.GnomeKeyringBackedSecureStore; import org.junit.Before; import org.junit.Test; @@ -60,8 +60,8 @@ public void saveTokenPair() { final TokenPair readValue = underTest.get(testKey); - assertEquals(tokenPair.AccessToken, readValue.AccessToken); - assertEquals(tokenPair.RefreshToken, readValue.RefreshToken); + assertEquals(tokenPair.getAccessToken(), readValue.getAccessToken()); + assertEquals(tokenPair.getRefreshToken(), readValue.getRefreshToken()); boolean deleted = underTest.delete(testKey); assertTrue(deleted); diff --git a/storage/src/test/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedTokenPairStoreTest.java b/src/test/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedTokenPairStoreTest.java similarity index 50% rename from storage/src/test/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedTokenPairStoreTest.java rename to src/test/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedTokenPairStoreTest.java index 8e469d44..ff81fe78 100644 --- a/storage/src/test/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedTokenPairStoreTest.java +++ b/src/test/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedTokenPairStoreTest.java @@ -1,11 +1,19 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.posix; +package com.microsoft.a4o.credentialstorage.storage.posix; -import com.microsoft.alm.secret.TokenPair; +import com.microsoft.a4o.credentialstorage.helpers.StringHelperTest; +import com.microsoft.a4o.credentialstorage.helpers.XmlHelper; +import com.microsoft.a4o.credentialstorage.secret.TokenPair; import org.junit.Before; import org.junit.Test; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import java.io.ByteArrayInputStream; import static org.junit.Assert.assertEquals; @@ -14,7 +22,8 @@ public class GnomeKeyringBackedTokenPairStoreTest { GnomeKeyringBackedTokenPairStore underTest; - private final String sampleAssessToken = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1uQ19WWmNBVGZNNXBPWW" + + private static final String SAMPLE_ASSESS_TOKEN = + "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6Ik1uQ19WWmNBVGZNNXBPWW" + "lKSE1iYTlnb0VLWSIsImtpZCI6Ik1uQ19WWmNBVGZNNXBPWWlKSE1iYTlnb0VLWSJ9.eyJhdWQiOiJodHRwcz" + "ovL21hbmFnZW1lbnQuY29yZS53aW5kb3dzLm5ldC8iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9" + "mOGNkZWYzMS1hMzFlLTRiNGEtOTNlNC01ZjU3MWU5MTI1NWEvIiwiaWF0IjoxNDU4NzUwOTc3LCJuYmYiOjE0NT" + @@ -30,7 +39,8 @@ public class GnomeKeyringBackedTokenPairStoreTest { "NQFecidci7Xr_aGSFTq1KMK2dwDz1LDdVbSx8wJP__LU3DDPzUR-eitSdXkMFLBzZpMA92nPhBAQWnks0xEtEd3" + "pK_Jerl82xaK5IhVzEhkh70deCDgGB_90DoxlGf93Aursq5I5WKRQ"; - private final String sampleRefreshToken = "AAABAAAAiL9Kn2Z27UubvWFPbm0gLWTuMRxgA2q_tw71qiUQeaQ2JiRdQOroj2" + + private static final String SAMPLE_REFRESH_TOKEN = + "AAABAAAAiL9Kn2Z27UubvWFPbm0gLWTuMRxgA2q_tw71qiUQeaQ2JiRdQOroj2" + "7iBaKg7AFEMyE-V_DdbHvY6SIkJJHstS_xfWN_2zquKaHTrHI_EgIX7ZS7Ik8ChNTcba8g8d4geT72x9mosR9HZkwY" + "eUN1y9wr9f5ECmiCCisDNUNk9bvx86ZnpsJ3DtsQyaPmqcSf5cxQ3XX7fjGljZ0JyWCeCdnNcKsvrBajfWIpW37K3wXpoC" + "NFNIthL--rcchCXHd1yOaBtSWZmhL2bObot00mOeQh42mp01JgNH2EtqStPUA3a63hIrUMLWSVNyxCA5xgMsryygro" + @@ -45,12 +55,42 @@ public void setUp() throws Exception { @Test public void serializeDeserialize_tokens() { - final TokenPair tokenPair = new TokenPair(sampleAssessToken, sampleRefreshToken); + final TokenPair tokenPair = new TokenPair(SAMPLE_ASSESS_TOKEN, SAMPLE_REFRESH_TOKEN); final String serialized = underTest.serialize(tokenPair); final TokenPair processed = underTest.deserialize(serialized); - assertEquals(tokenPair.AccessToken, processed.AccessToken); - assertEquals(tokenPair.RefreshToken, processed.RefreshToken); + assertEquals(tokenPair.getAccessToken(), processed.getAccessToken()); + assertEquals(tokenPair.getRefreshToken(), processed.getRefreshToken()); + } + + @Test + public void xmlSerialization_roundTrip() throws Exception { + final TokenPair tokenPair = + new TokenPair("9297fb18-46d0-4846-97ca-ab8dd3b55729", "d15281b1-03f1-4581-90d3-4527d9cf4147"); + final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + final DocumentBuilder builder = dbf.newDocumentBuilder(); + final Document serializationDoc = builder.newDocument(); + + final Element element = GnomeKeyringBackedTokenPairStore.toXml(tokenPair, serializationDoc); + + serializationDoc.appendChild(element); + final String actualXmlString = XmlHelper.toString(serializationDoc); + final String expectedXmlString = + "\n" + + "\n" + + " 9297fb18-46d0-4846-97ca-ab8dd3b55729\n" + + " d15281b1-03f1-4581-90d3-4527d9cf4147\n" + + ""; + StringHelperTest.assertLinesEqual(expectedXmlString, actualXmlString); + + final ByteArrayInputStream bais = new ByteArrayInputStream(actualXmlString.getBytes()); + final Document deserializationDoc = builder.parse(bais); + final Element rootNode = deserializationDoc.getDocumentElement(); + + final TokenPair actualTokenPair = GnomeKeyringBackedTokenPairStore.fromXml(rootNode); + + assertEquals(tokenPair.getAccessToken().getValue(), actualTokenPair.getAccessToken().getValue()); + assertEquals(tokenPair.getRefreshToken().getValue(), actualTokenPair.getRefreshToken().getValue()); } } \ No newline at end of file diff --git a/storage/src/test/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedTokenStoreIT.java b/src/test/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedTokenStoreIT.java similarity index 78% rename from storage/src/test/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedTokenStoreIT.java rename to src/test/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedTokenStoreIT.java index 61631b14..7468c929 100644 --- a/storage/src/test/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedTokenStoreIT.java +++ b/src/test/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedTokenStoreIT.java @@ -1,11 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.posix; +package com.microsoft.a4o.credentialstorage.storage.posix; -import com.microsoft.alm.secret.Token; -import com.microsoft.alm.secret.TokenType; -import com.microsoft.alm.storage.posix.internal.GnomeKeyringBackedSecureStore; +import com.microsoft.a4o.credentialstorage.secret.Token; +import com.microsoft.a4o.credentialstorage.secret.TokenType; +import com.microsoft.a4o.credentialstorage.storage.posix.internal.GnomeKeyringBackedSecureStore; import org.junit.Before; import org.junit.Test; @@ -37,7 +37,7 @@ public void saveToken() { final Token readValue = underTest.get(testKey); - assertEquals(token.Value, readValue.Value); + assertEquals(token.getValue(), readValue.getValue()); boolean deleted = underTest.delete(testKey); assertTrue(deleted); diff --git a/storage/src/test/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedTokenStoreTest.java b/src/test/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedTokenStoreTest.java similarity index 75% rename from storage/src/test/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedTokenStoreTest.java rename to src/test/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedTokenStoreTest.java index 6a074a66..251e9fe4 100644 --- a/storage/src/test/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedTokenStoreTest.java +++ b/src/test/java/com/microsoft/a4o/credentialstorage/storage/posix/GnomeKeyringBackedTokenStoreTest.java @@ -1,10 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.posix; +package com.microsoft.a4o.credentialstorage.storage.posix; -import com.microsoft.alm.secret.Token; -import com.microsoft.alm.secret.TokenType; +import com.microsoft.a4o.credentialstorage.secret.Token; +import com.microsoft.a4o.credentialstorage.secret.TokenType; import org.junit.Before; import org.junit.Test; @@ -27,7 +27,7 @@ public void serializeDeserialize() { final String serialized = underTest.serialize(token); final Token processed = underTest.deserialize(serialized) ; - assertEquals(token.Value, processed.Value); + assertEquals(token.getValue(), processed.getValue()); } } \ No newline at end of file diff --git a/storage/src/test/java/com/microsoft/alm/storage/posix/internal/GnomeKeyringLibraryIT.java b/src/test/java/com/microsoft/a4o/credentialstorage/storage/posix/internal/GnomeKeyringLibraryIT.java similarity index 96% rename from storage/src/test/java/com/microsoft/alm/storage/posix/internal/GnomeKeyringLibraryIT.java rename to src/test/java/com/microsoft/a4o/credentialstorage/storage/posix/internal/GnomeKeyringLibraryIT.java index 4ee8fcd7..4f7dbae2 100644 --- a/storage/src/test/java/com/microsoft/alm/storage/posix/internal/GnomeKeyringLibraryIT.java +++ b/src/test/java/com/microsoft/a4o/credentialstorage/storage/posix/internal/GnomeKeyringLibraryIT.java @@ -1,8 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.posix.internal; +package com.microsoft.a4o.credentialstorage.storage.posix.internal; +import com.microsoft.a4o.credentialstorage.storage.posix.internal.GnomeKeyringBackedSecureStore; +import com.microsoft.a4o.credentialstorage.storage.posix.internal.GnomeKeyringLibrary; import org.junit.Before; import org.junit.Test; diff --git a/storage/src/test/java/com/microsoft/alm/storage/windows/CredManagerBackedCredentialStoreIT.java b/src/test/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedCredentialStoreIT.java similarity index 87% rename from storage/src/test/java/com/microsoft/alm/storage/windows/CredManagerBackedCredentialStoreIT.java rename to src/test/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedCredentialStoreIT.java index 5a8260b5..fc31fccd 100644 --- a/storage/src/test/java/com/microsoft/alm/storage/windows/CredManagerBackedCredentialStoreIT.java +++ b/src/test/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedCredentialStoreIT.java @@ -1,10 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.windows; +package com.microsoft.a4o.credentialstorage.storage.windows; -import com.microsoft.alm.helpers.SystemHelper; -import com.microsoft.alm.secret.Credential; +import com.microsoft.a4o.credentialstorage.helpers.SystemHelper; +import com.microsoft.a4o.credentialstorage.secret.Credential; import org.junit.Before; import org.junit.Test; @@ -38,8 +38,8 @@ public void e2eTestStoreReadDelete() { Credential readCred = underTest.get(key); - assertEquals("Retrieved Credential.Username is different", username, credential.Username); - assertEquals("Retrieved Credential.Password is different", password, credential.Password); + assertEquals("Retrieved Credential.Username is different", username, credential.getUsername()); + assertEquals("Retrieved Credential.Password is different", password, credential.getPassword()); // The credential under the specified key should be deleted now, it's a good idea to manually verify this now boolean deleted = underTest.delete(key); diff --git a/storage/src/test/java/com/microsoft/alm/storage/windows/CredManagerBackedCredentialStoreTest.java b/src/test/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedCredentialStoreTest.java similarity index 84% rename from storage/src/test/java/com/microsoft/alm/storage/windows/CredManagerBackedCredentialStoreTest.java rename to src/test/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedCredentialStoreTest.java index dc4328a7..624b1bb4 100644 --- a/storage/src/test/java/com/microsoft/alm/storage/windows/CredManagerBackedCredentialStoreTest.java +++ b/src/test/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedCredentialStoreTest.java @@ -1,16 +1,13 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.windows; +package com.microsoft.a4o.credentialstorage.storage.windows; -import com.microsoft.alm.secret.Credential; +import com.microsoft.a4o.credentialstorage.secret.Credential; import org.junit.Before; import org.junit.Test; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; public class CredManagerBackedCredentialStoreTest { @@ -28,8 +25,8 @@ public void setup() throws Exception { public void testCreate() throws Exception { Credential credential= underTest.create(username, password); - assertEquals("Username not correct", username, credential.Username); - assertEquals("Password not correct", password, credential.Password); + assertEquals("Username not correct", username, credential.getUsername()); + assertEquals("Password not correct", password, credential.getPassword()); } @Test diff --git a/storage/src/test/java/com/microsoft/alm/storage/windows/CredManagerBackedTokenPairStoreIT.java b/src/test/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedTokenPairStoreIT.java similarity index 88% rename from storage/src/test/java/com/microsoft/alm/storage/windows/CredManagerBackedTokenPairStoreIT.java rename to src/test/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedTokenPairStoreIT.java index e7625439..a08945cc 100644 --- a/storage/src/test/java/com/microsoft/alm/storage/windows/CredManagerBackedTokenPairStoreIT.java +++ b/src/test/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedTokenPairStoreIT.java @@ -1,10 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.windows; +package com.microsoft.a4o.credentialstorage.storage.windows; -import com.microsoft.alm.helpers.SystemHelper; -import com.microsoft.alm.secret.TokenPair; +import com.microsoft.a4o.credentialstorage.helpers.SystemHelper; +import com.microsoft.a4o.credentialstorage.secret.TokenPair; import org.junit.Before; import org.junit.Test; @@ -38,8 +38,8 @@ public void saveTokenPair() { final TokenPair readValue = underTest.get(testKey); // Only save refresh token - assertEquals("", readValue.AccessToken.Value); - assertEquals(tokenPair.RefreshToken.Value, readValue.RefreshToken.Value); + assertEquals(underTest.getUsername(readValue), readValue.getAccessToken().getValue()); + assertEquals(tokenPair.getRefreshToken().getValue(), readValue.getRefreshToken().getValue()); boolean deleted = underTest.delete(testKey); assertTrue(deleted); diff --git a/storage/src/test/java/com/microsoft/alm/storage/windows/CredManagerBackedTokenStoreIT.java b/src/test/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedTokenStoreIT.java similarity index 82% rename from storage/src/test/java/com/microsoft/alm/storage/windows/CredManagerBackedTokenStoreIT.java rename to src/test/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedTokenStoreIT.java index 564ff5eb..a5cba8ee 100644 --- a/storage/src/test/java/com/microsoft/alm/storage/windows/CredManagerBackedTokenStoreIT.java +++ b/src/test/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedTokenStoreIT.java @@ -1,11 +1,11 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.windows; +package com.microsoft.a4o.credentialstorage.storage.windows; -import com.microsoft.alm.helpers.SystemHelper; -import com.microsoft.alm.secret.Token; -import com.microsoft.alm.secret.TokenType; +import com.microsoft.a4o.credentialstorage.helpers.SystemHelper; +import com.microsoft.a4o.credentialstorage.secret.Token; +import com.microsoft.a4o.credentialstorage.secret.TokenType; import org.junit.Before; import org.junit.Test; @@ -37,7 +37,7 @@ public void e2eTestStoreReadDelete() { Token readToken = underTest.get(key); - assertEquals("Retrieved token is different", token.Value, readToken.Value); + assertEquals("Retrieved token is different", token.getValue(), readToken.getValue()); // The token under the specified key should be deleted now, it's a good idea to manually verify this now boolean deleted = underTest.delete(key); diff --git a/storage/src/test/java/com/microsoft/alm/storage/windows/CredManagerBackedTokenStoreTest.java b/src/test/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedTokenStoreTest.java similarity index 82% rename from storage/src/test/java/com/microsoft/alm/storage/windows/CredManagerBackedTokenStoreTest.java rename to src/test/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedTokenStoreTest.java index 5d8fcb6b..3d796685 100644 --- a/storage/src/test/java/com/microsoft/alm/storage/windows/CredManagerBackedTokenStoreTest.java +++ b/src/test/java/com/microsoft/a4o/credentialstorage/storage/windows/CredManagerBackedTokenStoreTest.java @@ -1,10 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.windows; +package com.microsoft.a4o.credentialstorage.storage.windows; -import com.microsoft.alm.secret.Token; -import com.microsoft.alm.secret.TokenType; +import com.microsoft.a4o.credentialstorage.secret.Token; +import com.microsoft.a4o.credentialstorage.secret.TokenType; import org.junit.Before; import org.junit.Test; @@ -25,7 +25,7 @@ public void testCreate() throws Exception { final String secretValue = "my secret"; Token token = underTest.create("do not care", secretValue); - assertEquals("Secret not correct", secretValue, token.Value); + assertEquals("Secret not correct", secretValue, token.getValue()); } @Test diff --git a/storage/src/test/java/com/microsoft/alm/storage/windows/internal/CredAdvapi32IT.java b/src/test/java/com/microsoft/a4o/credentialstorage/storage/windows/internal/CredAdvapi32IT.java similarity index 96% rename from storage/src/test/java/com/microsoft/alm/storage/windows/internal/CredAdvapi32IT.java rename to src/test/java/com/microsoft/a4o/credentialstorage/storage/windows/internal/CredAdvapi32IT.java index 48bacbcc..24626ce8 100644 --- a/storage/src/test/java/com/microsoft/alm/storage/windows/internal/CredAdvapi32IT.java +++ b/src/test/java/com/microsoft/a4o/credentialstorage/storage/windows/internal/CredAdvapi32IT.java @@ -1,9 +1,10 @@ // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See License.txt in the project root. -package com.microsoft.alm.storage.windows.internal; +package com.microsoft.a4o.credentialstorage.storage.windows.internal; -import com.microsoft.alm.helpers.SystemHelper; +import com.microsoft.a4o.credentialstorage.helpers.SystemHelper; +import com.microsoft.a4o.credentialstorage.storage.windows.internal.CredAdvapi32; import com.sun.jna.LastErrorException; import com.sun.jna.Memory; import com.sun.jna.Pointer; diff --git a/storage/pom.xml b/storage/pom.xml deleted file mode 100644 index a436f2f7..00000000 --- a/storage/pom.xml +++ /dev/null @@ -1,198 +0,0 @@ - - - 4.0.0 - - - com.microsoft.alm - auth-lib-parent - 0.6.5 - - auth-secure-storage - jar - - Secure storage for storing credentials created by Authentication Library - Provides different kind of secure storage for storing secrets generated by the Authentication Library - https://java.visualstudio.com/ - - - 5.9.0 - - - - - - ${basedir}/../common/src/main/resources - ./ - - - ${basedir}/.. - ./ - false - - LICENSE.txt - - - - - - maven-enforcer-plugin - - - enforce-versions - - enforce - - - - - 11 - - - - - - - - maven-compiler-plugin - - 11 - 11 - - - - net.ju-n.maven.plugins - checksum-maven-plugin - 1.2 - - - verify - - artifacts - - - - - - SHA-256 - - - - - org.codehaus.gmavenplus - gmavenplus-plugin - - - gmavenplus-test - - compileTests - - - - gmavenplus-verify - verify - - execute - - - - - ${project.artifactId}-${project.version}.jar.sha256 - - - - - - - - - - org.codehaus.groovy - groovy-ant - 3.0.9 - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - - attach-javadocs - - jar - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - - - - - com.microsoft.alm - auth-common - - - - com.microsoft.alm - oauth2-useragent - - - - net.java.dev.jna - jna - ${jna.version} - - - net.java.dev.jna - jna-platform - ${jna.version} - - - - org.slf4j - slf4j-api - - - - org.slf4j - slf4j-nop - test - - - - junit - junit - test - - - org.mockito - mockito-core - test - - - - org.codehaus.groovy - groovy - test - - - diff --git a/storage/src/main/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedCredentialStore.java b/storage/src/main/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedCredentialStore.java deleted file mode 100644 index 80c99bf1..00000000 --- a/storage/src/main/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedCredentialStore.java +++ /dev/null @@ -1,91 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.storage.posix; - -import com.microsoft.alm.helpers.Debug; -import com.microsoft.alm.helpers.StringHelper; -import com.microsoft.alm.helpers.XmlHelper; -import com.microsoft.alm.secret.Credential; -import com.microsoft.alm.storage.posix.internal.GnomeKeyringBackedSecureStore; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import java.io.ByteArrayInputStream; -import java.io.InputStream; - -import static com.microsoft.alm.helpers.LoggingHelper.logError; - -public class GnomeKeyringBackedCredentialStore extends GnomeKeyringBackedSecureStore { - - private static final Logger logger = LoggerFactory.getLogger(GnomeKeyringBackedCredentialStore.class); - - @Override - protected Credential deserialize(final String secret) { - Debug.Assert(secret != null, "secret cannot be null"); - - try { - return fromXmlString(secret); - } catch (final Exception e) { - logError(logger, "Failed to deserialize credential.", e); - return null; - } - } - - static Credential fromXmlString(final String xmlString) { - final byte[] bytes = StringHelper.UTF8GetBytes(xmlString); - final ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes); - return fromXmlStream(inputStream); - } - - static Credential fromXmlStream(final InputStream source) { - final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - try { - final DocumentBuilder builder = dbf.newDocumentBuilder(); - final Document document = builder.parse(source); - final Element rootElement = document.getDocumentElement(); - - final Credential result = Credential.fromXml(rootElement); - - return result; - } - catch (final Exception e) { - throw new Error(e); - } - } - - @Override - protected String serialize(final Credential credential) { - Debug.Assert(credential != null, "Credential cannot be null"); - - return toXmlString(credential); - } - - static String toXmlString(final Credential credential) { - final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - try { - final DocumentBuilder builder = dbf.newDocumentBuilder(); - final Document document = builder.newDocument(); - - final Element element = credential.toXml(document); - document.appendChild(element); - - final String result = XmlHelper.toString(document); - - return result; - } - catch (final Exception e) { - throw new Error(e); - } - - } - - @Override - protected String getType() { - return "Credential"; - } -} diff --git a/storage/src/main/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedTokenPairStore.java b/storage/src/main/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedTokenPairStore.java deleted file mode 100644 index f5dac312..00000000 --- a/storage/src/main/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedTokenPairStore.java +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.storage.posix; - -import com.microsoft.alm.helpers.Debug; -import com.microsoft.alm.helpers.StringHelper; -import com.microsoft.alm.helpers.XmlHelper; -import com.microsoft.alm.secret.TokenPair; -import com.microsoft.alm.storage.posix.internal.GnomeKeyringBackedSecureStore; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.w3c.dom.Document; -import org.w3c.dom.Element; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import java.io.ByteArrayInputStream; -import java.io.InputStream; - -import static com.microsoft.alm.helpers.LoggingHelper.logError; - -public class GnomeKeyringBackedTokenPairStore extends GnomeKeyringBackedSecureStore { - - private static final Logger logger = LoggerFactory.getLogger(GnomeKeyringBackedTokenPairStore.class); - - @Override - protected String serialize(final TokenPair tokenPair) { - Debug.Assert(tokenPair != null, "TokenPair cannot be null"); - - return TokenPair.toXmlString(tokenPair); - } - - @Override - protected TokenPair deserialize(final String secret) { - Debug.Assert(secret != null, "secret cannot be null"); - - try { - return TokenPair.fromXmlString(secret); - } catch (final Exception e) { - logError(logger, "Failed to deserialize the stored secret. Return null.", e); - return null; - } - } - - @Override - protected String getType() { - return "OAuth2Token"; - } -} diff --git a/storage/src/test/groovy/com/microsoft/alm/storage/macosx/FifoProcess.groovy b/storage/src/test/groovy/com/microsoft/alm/storage/macosx/FifoProcess.groovy deleted file mode 100644 index 088e5f5f..00000000 --- a/storage/src/test/groovy/com/microsoft/alm/storage/macosx/FifoProcess.groovy +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) Microsoft. All rights reserved. - * Licensed under the MIT license. See License.txt in the project root. - */ - -package com.microsoft.alm.storage.macosx; - -import com.microsoft.alm.storage.TestProcess - -class FifoProcess extends TestProcess { - - String[] expectedCommand = null; - int expectedExitCode = 0; - String expectedStandardInput = null; - - public FifoProcess(final String input) { - super(input) - } - - public FifoProcess(final String input, final String error) { - super(input, error) - } - - @Override - int waitFor() throws InterruptedException { - if (expectedStandardInput != null) { - final def actualStandardInput = getOutput() - assert actualStandardInput == expectedStandardInput - } - return expectedExitCode - } -} diff --git a/storage/src/test/groovy/com/microsoft/alm/storage/macosx/FifoProcessFactory.groovy b/storage/src/test/groovy/com/microsoft/alm/storage/macosx/FifoProcessFactory.groovy deleted file mode 100644 index c213a0a9..00000000 --- a/storage/src/test/groovy/com/microsoft/alm/storage/macosx/FifoProcessFactory.groovy +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright (c) Microsoft. All rights reserved. - * Licensed under the MIT license. See License.txt in the project root. - */ - -package com.microsoft.alm.storage.macosx; - -import com.microsoft.alm.oauth2.useragent.subprocess.TestableProcess -import com.microsoft.alm.oauth2.useragent.subprocess.TestableProcessFactory - -class FifoProcessFactory implements TestableProcessFactory { - - private final List processes = new ArrayList<>(); - - public FifoProcessFactory(final FifoProcess... processes) { - this.processes.addAll(processes) - } - - @Override TestableProcess create(final String... command) throws IOException { - def process = this.processes.remove(0) - - if (process.expectedCommand != null) { - def expectedCommandList = process.expectedCommand.toList() - def actualCommandList = command.toList() - assert expectedCommandList == actualCommandList - } - - return process - } -} diff --git a/storage/src/test/groovy/com/microsoft/alm/storage/macosx/KeychainSecurityCliStoreTest.groovy b/storage/src/test/groovy/com/microsoft/alm/storage/macosx/KeychainSecurityCliStoreTest.groovy deleted file mode 100644 index 545ef66a..00000000 --- a/storage/src/test/groovy/com/microsoft/alm/storage/macosx/KeychainSecurityCliStoreTest.groovy +++ /dev/null @@ -1,376 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.storage.macosx - -import com.microsoft.alm.helpers.Environment -import com.microsoft.alm.helpers.StringHelper -import com.microsoft.alm.oauth2.useragent.subprocess.DefaultProcessFactory -import com.microsoft.alm.oauth2.useragent.subprocess.TestableProcessFactory -import com.microsoft.alm.secret.Credential -import com.microsoft.alm.secret.Token -import com.microsoft.alm.secret.TokenType -import groovy.transform.CompileStatic -import org.junit.Ignore -import org.junit.Test - -/** - * A class to test {@see KeychainSecurityCliStore}. - */ -@CompileStatic -public class KeychainSecurityCliStoreTest { - - static final String USER_NAME = "chuck.norris" - static final String PASSWORD = "roundhouse" - static final String PASSWORD2 = "roundhouse kick" - static final String VSTS_ACCOUNT = "https://example.visualstudio.com" - static final String TARGET_NAME = "git:${VSTS_ACCOUNT}" - static final String SAMPLE_CREDENTIAL_METADATA = """\ -keychain: "/Users/${USER_NAME}/Library/Keychains/login.keychain" -class: "genp" -attributes: - 0x00000007 ="${TARGET_NAME}" - 0x00000008 = - "acct"="${USER_NAME}" - "cdat"=0x32303135313030353139343332355A00 "20151005194325Z\\000" - "crtr"="aapl" - "cusi"= - "desc"="Credential" - "gena"= - "icmt"= - "invi"= - "mdat"=0x32303135313030353139343332355A00 "20151005194325Z\\000" - "nega"= - "prot"= - "scrp"= - "svce"="${TARGET_NAME}" - "type"= -""" - - static final String SAMPLE_TOKEN_METADATA = """\ -keychain: "/Users/${USER_NAME}/Library/Keychains/login.keychain" -class: "genp" -attributes: - 0x00000007 ="${TARGET_NAME}" - 0x00000008 = - "acct"="Personal Access Token" - "cdat"=0x32303135313030353139343332355A00 "20151005194325Z\\000" - "crtr"="aapl" - "cusi"= - "desc"="Token" - "gena"= - "icmt"= - "invi"= - "mdat"=0x32303135313030353139343332355A00 "20151005194325Z\\000" - "nega"= - "prot"= - "scrp"= - "svce"="${TARGET_NAME}" - "type"= -""" - - @Test public void parseKeychainMetaData_typical() { - - def actual = KeychainSecurityCliStore.parseKeychainMetaData(SAMPLE_CREDENTIAL_METADATA) - - def expected = [ - "keychain": "/Users/${USER_NAME}/Library/Keychains/login.keychain", - "class": "genp", - "0x00000007": TARGET_NAME, - "0x00000008": null, - "acct": USER_NAME, - // Not supported: "cdat" : '0x32303135313030353139343332355A00 "20151005194325Z\\000"', - // Not supported: "crtr" : "aapl", - "cusi" : null, - "desc" : "Credential", - "gena" : null, - "icmt" : null, - "invi" : null, - // Not supported: "mdat" : '0x32303135313030353139343332355A00 "20151005194325Z\\000"', - "nega" : null, - "prot" : null, - "scrp" : null, - "svce" : TARGET_NAME, - "type" : null, - ] - assert expected == actual - } - - @Test public void parseMetadataLine() { - def input = '''keychain: "/Users/chuck.norris/Library/Keychains/login.keychain"''' - def destination = [:] - - KeychainSecurityCliStore.parseMetadataLine(input, destination) - - assert ["keychain" : "/Users/chuck.norris/Library/Keychains/login.keychain"] == destination - } - - @Test public void parseMetadataLine_containsDoubleQuote() { - def input = '''password: "A string with "double quotes" inside"''' - def destination = [:] - - KeychainSecurityCliStore.parseMetadataLine(input, destination) - - assert ["password" : 'A string with "double quotes" inside'] == destination - } - - @Test public void parseAttributeLine_stringKeyBlobString() { - def input = ''' "acct"="chuck.norris"''' - def destination = [:] - - KeychainSecurityCliStore.parseAttributeLine(input, destination) - - assert ["acct" : "chuck.norris"] == destination - } - - @Test public void parseAttributeLine_stringKeyBlobStringContainsDoubleQuote() { - def input = ''' "desc"="A string with "double quotes" inside"''' - def destination = [:] - - KeychainSecurityCliStore.parseAttributeLine(input, destination) - - assert ["desc" : 'A string with "double quotes" inside'] == destination - } - - @Test public void parseAttributeLine_stringKeyBlobNull() { - def input = ''' "acct"=''' - def destination = [:] - - KeychainSecurityCliStore.parseAttributeLine(input, destination) - - assert ["acct" : null] == destination - } - - @Test public void parseAttributeLine_hexKeyBlobString() { - def input = ''' 0x00000007 ="git:https://example.visualstudio.com"''' - def destination = [:] - - KeychainSecurityCliStore.parseAttributeLine(input, destination) - - assert ["0x00000007" : "git:https://example.visualstudio.com"] == destination - } - - @Test public void parseAttributeLine_hexKeyBlobNull() { - def input = ''' 0x00000008 =''' - def destination = [:] - - KeychainSecurityCliStore.parseAttributeLine(input, destination) - - assert ["0x00000008" : null] == destination - } - - @Test public void simulatedProbing_keychainIsAvailable() { - def showInfo = new FifoProcess(StringHelper.Empty) - showInfo.with { - expectedCommand = ["/usr/bin/security", "show-keychain-info"] - expectedExitCode = 0 - } - - def processFactory = new FifoProcessFactory( - showInfo, - ) - probingTest(processFactory, true) - } - - @Test public void simulatedProbing_keychainIsNotAvailable() { - def showInfo = new FifoProcess(StringHelper.Empty) - showInfo.with { - expectedCommand = ["/usr/bin/security", "show-keychain-info"] - expectedExitCode = 36 - } - - def processFactory = new FifoProcessFactory( - showInfo, - ) - probingTest(processFactory, false) - } - - @Ignore("Needs to be run manually, in interactive mode, because the Keychain needs a desktop") - @Test public void interactiveProbing() { - def processFactory = new DefaultProcessFactory() - probingTest(processFactory, true) - } - - static void probingTest(final TestableProcessFactory processFactory, final boolean expected) { - final def store = new KeychainSecurityCliStore(processFactory) - - final def actual = store.isKeychainAvailable() - - assert actual == expected - } - - @Test public void simulatedInteraction() { - def deleteCredentialSuccess = new FifoProcess(SAMPLE_CREDENTIAL_METADATA, "password has been deleted.") - deleteCredentialSuccess.with { - expectedCommand = ["/usr/bin/security", "delete-generic-password", "-s", TARGET_NAME, "-D", - KeychainSecurityCliStore.SecretKind.Credential.name()] - } - - def deleteCredentialFailure = new FifoProcess(StringHelper.Empty, "security: SecKeychainSearchCopyNext: The specified item could not be found in the keychain.") - deleteCredentialFailure.with { - expectedCommand = ["/usr/bin/security", "delete-generic-password", "-s", TARGET_NAME, "-D", - KeychainSecurityCliStore.SecretKind.Credential.name()] - expectedExitCode = 44 - } - - def findNoCredential = new FifoProcess(StringHelper.Empty, "security: SecKeychainSearchCopyNext: The specified item could not be found in the keychain.") - findNoCredential.with { - expectedCommand = ["/usr/bin/security", "find-generic-password", "-s", TARGET_NAME, "-D", "Credential", "-g"] - expectedExitCode = 44 - } - - def addCredential = new FifoProcess(StringHelper.Empty) - addCredential.with { - expectedCommand = ["/usr/bin/security", "-i"] - expectedStandardInput = """add-generic-password -U -a ${USER_NAME} -s ${TARGET_NAME} -w ${PASSWORD} -D Credential""" + Environment.NewLine - expectedExitCode = 0 - } - - def findCredential = new FifoProcess(SAMPLE_CREDENTIAL_METADATA, """password: "${PASSWORD}" -""") - findCredential.with { - expectedCommand = ["/usr/bin/security", "find-generic-password", "-s", TARGET_NAME, "-D", "Credential", "-g"] - expectedExitCode = 0 - } - - def updateCredential = new FifoProcess(StringHelper.Empty) - updateCredential.with { - expectedCommand = ["/usr/bin/security", "-i"] - expectedStandardInput = """add-generic-password -U -a ${USER_NAME} -s ${TARGET_NAME} -w "${PASSWORD2}" -D Credential""" + Environment.NewLine - expectedExitCode = 0 - } - - def findUpdatedCredential = new FifoProcess(SAMPLE_CREDENTIAL_METADATA, """password: "${PASSWORD2}" -""") - findUpdatedCredential.with { - expectedCommand = ["/usr/bin/security", "find-generic-password", "-s", TARGET_NAME, "-D", "Credential", "-g"] - expectedExitCode = 0 - } - - - def deleteTokenSuccess = new FifoProcess(SAMPLE_TOKEN_METADATA, "password has been deleted.") - deleteTokenSuccess.with { - expectedCommand = ["/usr/bin/security", "delete-generic-password", "-s", TARGET_NAME, "-D", - KeychainSecurityCliStore.SecretKind.Token.name()] - } - - def deleteTokenFailure = new FifoProcess(StringHelper.Empty, "security: SecKeychainSearchCopyNext: The specified item could not be found in the keychain.") - deleteTokenFailure.with { - expectedCommand = ["/usr/bin/security", "delete-generic-password", "-s", TARGET_NAME, "-D", - KeychainSecurityCliStore.SecretKind.Token.name()] - expectedExitCode = 44 - } - - def findNoToken = new FifoProcess(StringHelper.Empty, "security: SecKeychainSearchCopyNext: The specified item could not be found in the keychain.") - findNoToken.with { - expectedCommand = ["/usr/bin/security", "find-generic-password", "-s", TARGET_NAME, "-D", "Token", "-g"] - expectedExitCode = 44 - } - - def addToken = new FifoProcess(StringHelper.Empty) - addToken.with { - expectedCommand = ["/usr/bin/security", "-i"] - expectedStandardInput = """add-generic-password -U -a "Personal Access Token" -s ${TARGET_NAME} -w ${PASSWORD} -D Token""" + Environment.NewLine - expectedExitCode = 0 - } - - def findToken = new FifoProcess(SAMPLE_TOKEN_METADATA, """password: "${PASSWORD}" -""") - findToken.with { - expectedCommand = ["/usr/bin/security", "find-generic-password", "-s", TARGET_NAME, "-D", "Token", "-g"] - expectedExitCode = 0 - } - - def updateToken = new FifoProcess(StringHelper.Empty) - updateToken.with { - expectedCommand = ["/usr/bin/security", "-i"] - expectedStandardInput = """add-generic-password -U -a "Personal Access Token" -s ${TARGET_NAME} -w "${PASSWORD2}" -D Token""" + Environment.NewLine - expectedExitCode = 0 - } - - def findUpdatedToken = new FifoProcess(SAMPLE_TOKEN_METADATA, """password: "${PASSWORD2}" -""") - findUpdatedToken.with { - expectedCommand = ["/usr/bin/security", "find-generic-password", "-s", TARGET_NAME, "-D", "Token", "-g"] - expectedExitCode = 0 - } - - def processFactory = new FifoProcessFactory( - deleteCredentialSuccess, - deleteCredentialFailure, - findNoCredential, - addCredential, - findCredential, - updateCredential, - findUpdatedCredential, - deleteTokenSuccess, - deleteTokenFailure, - findNoToken, - addToken, - findToken, - updateToken, - findUpdatedToken, - ) - endToEndTest(processFactory) - } - - @Ignore("Needs to be run manually, in interactive mode, because the Keychain needs a desktop") - @Test public void interactiveInteraction() { - def processFactory = new DefaultProcessFactory() - endToEndTest(processFactory) - } - - static void endToEndTest(final TestableProcessFactory processFactory) { - final def store = new KeychainSecurityCliStore(processFactory) - final def credential = new Credential(USER_NAME, PASSWORD) - - // potentially delete an old entry from a previous run of this test - store.deleteByKind(TARGET_NAME, KeychainSecurityCliStore.SecretKind.Credential) - // there should be nothing there, yet this call should not fail - store.deleteByKind(TARGET_NAME, KeychainSecurityCliStore.SecretKind.Credential) - - final def nullCredential = store.readCredentials(TARGET_NAME) - - assert nullCredential == null - - store.writeCredential(TARGET_NAME, credential) - - final def actualCredential = store.readCredentials(TARGET_NAME) - - assert credential == actualCredential - - final def updatedCredential = new Credential(USER_NAME, PASSWORD2) - - store.writeCredential(TARGET_NAME, updatedCredential) - - final def actualUpdatedCredential = store.readCredentials(TARGET_NAME) - - assert updatedCredential == actualUpdatedCredential - - - final def token = new Token(PASSWORD, TokenType.Personal) - - // potentially delete an old entry from a previous run of this test - store.deleteByKind(TARGET_NAME, KeychainSecurityCliStore.SecretKind.Token) - // there should be nothing there, yet this call should not fail - store.deleteByKind(TARGET_NAME, KeychainSecurityCliStore.SecretKind.Token) - - final def nullToken = store.readToken(TARGET_NAME) - - assert nullToken == null - - store.writeToken(TARGET_NAME, token) - - final def actualToken = store.readToken(TARGET_NAME) - - assert token == actualToken - - final def updatedToken = new Token(PASSWORD2, TokenType.Personal) - - store.writeToken(TARGET_NAME, updatedToken) - - final def actualUpdatedToken = store.readToken(TARGET_NAME) - - assert updatedToken == actualUpdatedToken - } -} diff --git a/storage/src/test/java/com/microsoft/alm/storage/TestProcess.java b/storage/src/test/java/com/microsoft/alm/storage/TestProcess.java deleted file mode 100644 index bf332e01..00000000 --- a/storage/src/test/java/com/microsoft/alm/storage/TestProcess.java +++ /dev/null @@ -1,63 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.storage; - -import com.microsoft.alm.oauth2.useragent.subprocess.TestableProcess; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.UnsupportedEncodingException; -import java.nio.charset.Charset; - -public class TestProcess implements TestableProcess -{ - - private static final Charset UTF_8 = Charset.forName("UTF-8"); - private final ByteArrayInputStream errorStream; - private final ByteArrayInputStream inputStream; - private final ByteArrayOutputStream outputStream; - - public TestProcess(final String input) - { - this(input, ""); - } - - public TestProcess(final String input, final String error) - { - inputStream = new ByteArrayInputStream(input.getBytes(UTF_8)); - errorStream = new ByteArrayInputStream(error.getBytes(UTF_8)); - outputStream = new ByteArrayOutputStream(); - } - - @Override - public InputStream getErrorStream() - { - return errorStream; - } - - @Override - public InputStream getInputStream() - { - return inputStream; - } - - @Override - public OutputStream getOutputStream() - { - return outputStream; - } - - public String getOutput() throws UnsupportedEncodingException - { - return outputStream.toString(UTF_8.name()); - } - - @Override - public int waitFor() throws InterruptedException - { - return 0; - } -} diff --git a/storage/src/test/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedCredentialStoreTest.java b/storage/src/test/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedCredentialStoreTest.java deleted file mode 100644 index 5c5c7359..00000000 --- a/storage/src/test/java/com/microsoft/alm/storage/posix/GnomeKeyringBackedCredentialStoreTest.java +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. See License.txt in the project root. - -package com.microsoft.alm.storage.posix; - -import com.microsoft.alm.secret.Credential; -import org.junit.Before; -import org.junit.Test; - -import static org.junit.Assert.assertEquals; - -public class GnomeKeyringBackedCredentialStoreTest { - - GnomeKeyringBackedCredentialStore underTest; - - @Before - public void setUp() throws Exception { - underTest = new GnomeKeyringBackedCredentialStore(); - } - - @Test - public void serializeDeserialize_specialChars() { - final String username = "!@#$%^&*~"; - final String password = ":'\"/"; - final Credential cred = new Credential(username, password); - final String serialized = underTest.serialize(cred); - final Credential processedCred = underTest.deserialize(serialized); - assertEquals(username, processedCred.Username); - assertEquals(password, processedCred.Password); - } - -} \ No newline at end of file