1 /*
2  * Copyright 2014-2020 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License").
5  * You may not use this file except in compliance with the License.
6  * A copy of the License is located at
7  *
8  *  http://aws.amazon.com/apache2.0
9  *
10  * or in the "license" file accompanying this file. This file is distributed
11  * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12  * express or implied. See the License for the specific language governing
13  * permissions and limitations under the License.
14  */

15 package com.amazonaws.auth.profile.internal;
16
17 import com.amazonaws.SdkClientException;
18 import com.amazonaws.annotation.SdkInternalApi;
19 import com.amazonaws.util.StringUtils;
20
21 import org.apache.commons.logging.Log;
22 import org.apache.commons.logging.LogFactory;
23
24 import java.io.File;
25 import java.io.FileInputStream;
26 import java.io.IOException;
27 import java.io.InputStream;
28 import java.util.HashMap;
29 import java.util.LinkedHashMap;
30 import java.util.Map;
31 import java.util.Map.Entry;
32 import java.util.Scanner;
33
34 /**
35  * Class to load a CLI style config or credentials file. Performs only basic validation on
36  * properties and profiles.
37  */

38 @SdkInternalApi
39 public class BasicProfileConfigLoader {
40
41     private static final Log LOG = LogFactory.getLog(BasicProfileConfigLoader.class);
42
43     public static final BasicProfileConfigLoader INSTANCE = new BasicProfileConfigLoader();
44
45     private BasicProfileConfigLoader() {
46     }
47
48     public AllProfiles loadProfiles(File file) {
49         if (file == null) {
50             throw new IllegalArgumentException(
51                     "Unable to load AWS profiles: specified file is null.");
52         }
53
54         if (!file.exists() || !file.isFile()) {
55             throw new IllegalArgumentException(
56                     "AWS credential profiles file not found in the given path: " +
57                     file.getAbsolutePath());
58         }
59
60         FileInputStream fis = null;
61         try {
62             fis = new FileInputStream(file);
63             return loadProfiles(fis);
64         } catch (IOException ioe) {
65             throw new SdkClientException(
66                     "Unable to load AWS credential profiles file at: " + file.getAbsolutePath(),
67                     ioe);
68         } finally {
69             if (fis != null) {
70                 try {
71                     fis.close();
72                 } catch (IOException ioe) {
73                 }
74             }
75         }
76     }
77
78     /**
79      * Loads the credential profiles from the given input stream.
80      *
81      * @param is input stream from where the profile details are read.
82      */

83     private AllProfiles loadProfiles(InputStream is) throws IOException {
84         ProfilesConfigFileLoaderHelper helper = new ProfilesConfigFileLoaderHelper();
85         Map<String, Map<String, String>> allProfileProperties = helper
86                 .parseProfileProperties(new Scanner(is, StringUtils.UTF8.name()));
87
88         // Convert the loaded property map to credential objects
89         Map<String, BasicProfile> profilesByName = new LinkedHashMap<String, BasicProfile>();
90
91         for (Entry<String, Map<String, String>> entry : allProfileProperties.entrySet()) {
92             String profileName = entry.getKey();
93             Map<String, String> properties = entry.getValue();
94
95             if (profileName.startsWith("profile ")) {
96                 LOG.warn(
97                         "Your profile name includes a 'profile ' prefix. This is considered part of the profile name in the " +
98                         "Java SDK, so you will need to include this prefix in your profile name when you reference this " +
99                         "profile from your Java code.");
100             }
101
102             assertParameterNotEmpty(profileName,
103                                     "Unable to load properties from profile: Profile name is empty.");
104             profilesByName.put(profileName, new BasicProfile(profileName, properties));
105         }
106
107         return new AllProfiles(profilesByName);
108     }
109
110     /**
111      * <p> Asserts that the specified parameter value is neither <code>empty</code> nor null, and if
112      * it is, throws a <code>SdkClientException</code> with the specified error message. </p>
113      *
114      * @param parameterValue The parameter value being checked.
115      * @param errorMessage   The error message to include in the SdkClientException if the
116      *                       specified parameter value is empty.
117      */

118     private void assertParameterNotEmpty(String parameterValue, String errorMessage) {
119         if (StringUtils.isNullOrEmpty(parameterValue)) {
120             throw new SdkClientException(errorMessage);
121         }
122     }
123
124     /**
125      * Implementation of AbstractProfilesConfigFileScanner that groups profile properties into a map
126      * while scanning through the credentials profile.
127      */

128     private static class ProfilesConfigFileLoaderHelper extends AbstractProfilesConfigFileScanner {
129
130         /**
131          * Map from the parsed profile name to the map of all the property values included the
132          * specific profile
133          */

134         protected final Map<String, Map<String, String>> allProfileProperties = new LinkedHashMap<String, Map<String, String>>();
135
136         /**
137          * Parses the input and returns a map of all the profile properties.
138          */

139         public Map<String, Map<String, String>> parseProfileProperties(Scanner scanner) {
140             allProfileProperties.clear();
141             run(scanner);
142             return new LinkedHashMap<String, Map<String, String>>(allProfileProperties);
143         }
144
145         @Override
146         protected void onEmptyOrCommentLine(String profileName, String line) {
147             // Ignore empty or comment line
148         }
149
150         @Override
151         protected void onProfileStartingLine(String newProfileName, String line) {
152             // If the same profile name has already been declared, clobber the
153             // previous one
154             allProfileProperties.put(newProfileName, new HashMap<String, String>());
155         }
156
157         @Override
158         protected void onProfileEndingLine(String prevProfileName) {
159             // No-op
160         }
161
162         @Override
163         protected void onProfileProperty(String profileName, String propertyKey,
164                                          String propertyValue, boolean isSupportedProperty,
165                                          String line) {
166             Map<String, String> properties = allProfileProperties.get(profileName);
167
168             if (properties.containsKey(propertyKey)) {
169                 throw new IllegalArgumentException(
170                         "Duplicate property values for [" + propertyKey + "].");
171             }
172
173             properties.put(propertyKey, propertyValue);
174         }
175
176         @Override
177         protected void onEndOfFile() {
178             // No-op
179         }
180     }
181
182 }
183