View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.cling.invoker.mvnenc.goals;
20  
21  import javax.inject.Inject;
22  import javax.inject.Named;
23  import javax.inject.Singleton;
24  
25  import java.util.List;
26  import java.util.Map;
27  import java.util.Objects;
28  
29  import org.apache.maven.api.cli.mvnenc.EncryptOptions;
30  import org.apache.maven.api.services.MessageBuilderFactory;
31  import org.apache.maven.cling.invoker.mvnenc.EncryptContext;
32  import org.codehaus.plexus.components.secdispatcher.DispatcherMeta;
33  import org.codehaus.plexus.components.secdispatcher.SecDispatcher;
34  import org.codehaus.plexus.components.secdispatcher.model.Config;
35  import org.codehaus.plexus.components.secdispatcher.model.ConfigProperty;
36  import org.codehaus.plexus.components.secdispatcher.model.SettingsSecurity;
37  import org.jline.consoleui.elements.ConfirmChoice;
38  import org.jline.consoleui.prompt.ConfirmResult;
39  import org.jline.consoleui.prompt.ConsolePrompt;
40  import org.jline.consoleui.prompt.PromptResultItemIF;
41  import org.jline.consoleui.prompt.builder.ListPromptBuilder;
42  import org.jline.consoleui.prompt.builder.PromptBuilder;
43  import org.jline.reader.Candidate;
44  import org.jline.reader.Completer;
45  import org.jline.reader.LineReader;
46  import org.jline.reader.ParsedLine;
47  import org.jline.utils.Colors;
48  
49  import static org.apache.maven.cling.invoker.mvnenc.EncryptInvoker.BAD_OPERATION;
50  import static org.apache.maven.cling.invoker.mvnenc.EncryptInvoker.OK;
51  
52  /**
53   * The "init" goal.
54   */
55  @Singleton
56  @Named("init")
57  public class Init extends GoalSupport {
58      private static final String NONE = "__none__";
59  
60      @Inject
61      public Init(MessageBuilderFactory messageBuilderFactory, SecDispatcher secDispatcher) {
62          super(messageBuilderFactory, secDispatcher);
63      }
64  
65      @Override
66      public int execute(EncryptContext context) throws Exception {
67          context.addInHeader(context.style.italic().bold().foreground(Colors.rgbColor("yellow")), "goal: init");
68          context.addInHeader("");
69  
70          ConsolePrompt prompt = context.prompt;
71  
72          EncryptOptions options = (EncryptOptions) context.invokerRequest.options();
73          boolean force = options.force().orElse(false);
74          boolean yes = options.yes().orElse(false);
75  
76          if (configExists() && !force) {
77              context.terminal
78                      .writer()
79                      .println(
80                              messageBuilderFactory
81                                      .builder()
82                                      .error(
83                                              "Error: configuration exist. Use --force if you want to reset existing configuration."));
84              return BAD_OPERATION;
85          }
86  
87          SettingsSecurity config = secDispatcher.readConfiguration(true);
88  
89          // reset config
90          config.setDefaultDispatcher(null);
91          config.getConfigurations().clear();
92  
93          Map<String, PromptResultItemIF> result = prompt.prompt(
94                  context.header, dispatcherPrompt(prompt.getPromptBuilder()).build());
95          if (result == null) {
96              throw new InterruptedException();
97          }
98          if (NONE.equals(result.get("defaultDispatcher").getResult())) {
99              context.terminal
100                     .writer()
101                     .println(messageBuilderFactory
102                             .builder()
103                             .warning(
104                                     "Maven4 SecDispatcher disabled; Maven3 fallback may still work, use `mvnenc diag` to check")
105                             .build());
106             secDispatcher.writeConfiguration(config);
107             return OK;
108         }
109         config.setDefaultDispatcher(result.get("defaultDispatcher").getResult());
110 
111         DispatcherMeta meta = secDispatcher.availableDispatchers().stream()
112                 .filter(d -> Objects.equals(config.getDefaultDispatcher(), d.name()))
113                 .findFirst()
114                 .orElseThrow();
115         if (!meta.fields().isEmpty()) {
116             result = prompt.prompt(
117                     context.header,
118                     configureDispatcher(context, meta, prompt.getPromptBuilder())
119                             .build());
120             if (result == null) {
121                 throw new InterruptedException();
122             }
123 
124             List<Map.Entry<String, PromptResultItemIF>> editables = result.entrySet().stream()
125                     .filter(e -> e.getValue().getResult().contains("$"))
126                     .toList();
127             if (!editables.isEmpty()) {
128                 context.addInHeader("");
129                 context.addInHeader("Please customize the editable value:");
130                 Map<String, PromptResultItemIF> editMap;
131                 for (Map.Entry<String, PromptResultItemIF> editable : editables) {
132                     String template = editable.getValue().getResult();
133                     String prefix = template.substring(0, template.indexOf("$"));
134                     editMap = prompt.prompt(
135                             context.header,
136                             prompt.getPromptBuilder()
137                                     .createInputPrompt()
138                                     .name("edit")
139                                     .message(template)
140                                     .addCompleter(new Completer() {
141                                         @Override
142                                         public void complete(
143                                                 LineReader reader, ParsedLine line, List<Candidate> candidates) {
144                                             if (!line.line().startsWith(prefix)) {
145                                                 candidates.add(
146                                                         new Candidate(prefix, prefix, null, null, null, null, false));
147                                             }
148                                         }
149                                     })
150                                     .addPrompt()
151                                     .build());
152                     if (editMap == null) {
153                         throw new InterruptedException();
154                     }
155                     result.put(editable.getKey(), editMap.get("edit"));
156                 }
157             }
158 
159             Config dispatcherConfig = new Config();
160             dispatcherConfig.setName(meta.name());
161             for (DispatcherMeta.Field field : meta.fields()) {
162                 ConfigProperty property = new ConfigProperty();
163                 property.setName(field.getKey());
164                 property.setValue(result.get(field.getKey()).getResult());
165                 dispatcherConfig.addProperty(property);
166             }
167             if (!dispatcherConfig.getProperties().isEmpty()) {
168                 config.addConfiguration(dispatcherConfig);
169             }
170         }
171 
172         if (yes) {
173             secDispatcher.writeConfiguration(config);
174         } else {
175             context.addInHeader("");
176             context.addInHeader("Values set:");
177             context.addInHeader("defaultDispatcher=" + config.getDefaultDispatcher());
178             for (Config c : config.getConfigurations()) {
179                 context.addInHeader("  dispatcherName=" + c.getName());
180                 for (ConfigProperty cp : c.getProperties()) {
181                     context.addInHeader("    " + cp.getName() + "=" + cp.getValue());
182                 }
183             }
184 
185             result = prompt.prompt(
186                     context.header, confirmPrompt(prompt.getPromptBuilder()).build());
187             ConfirmResult confirm = (ConfirmResult) result.get("confirm");
188             if (confirm.getConfirmed() == ConfirmChoice.ConfirmationValue.YES) {
189                 context.terminal
190                         .writer()
191                         .println(messageBuilderFactory
192                                 .builder()
193                                 .info("Writing out the configuration...")
194                                 .build());
195                 secDispatcher.writeConfiguration(config);
196             } else {
197                 context.terminal
198                         .writer()
199                         .println(messageBuilderFactory
200                                 .builder()
201                                 .warning("Values not accepted; not saving configuration.")
202                                 .build());
203                 return BAD_OPERATION;
204             }
205         }
206 
207         return OK;
208     }
209 
210     protected PromptBuilder confirmPrompt(PromptBuilder promptBuilder) {
211         promptBuilder
212                 .createConfirmPromp()
213                 .name("confirm")
214                 .message("Are values above correct?")
215                 .defaultValue(ConfirmChoice.ConfirmationValue.YES)
216                 .addPrompt();
217         return promptBuilder;
218     }
219 
220     protected PromptBuilder dispatcherPrompt(PromptBuilder promptBuilder) {
221         ListPromptBuilder listPromptBuilder = promptBuilder
222                 .createListPrompt()
223                 .name("defaultDispatcher")
224                 .message("Which dispatcher you want to use as default?");
225         listPromptBuilder
226                 .newItem()
227                 .name(NONE)
228                 .text("None (disable MavenSecDispatcher)")
229                 .add();
230         for (DispatcherMeta meta : secDispatcher.availableDispatchers()) {
231             if (!meta.isHidden()) {
232                 listPromptBuilder
233                         .newItem()
234                         .name(meta.name())
235                         .text(meta.displayName())
236                         .add();
237             }
238         }
239         listPromptBuilder.addPrompt();
240         return promptBuilder;
241     }
242 
243     private PromptBuilder configureDispatcher(
244             EncryptContext context, DispatcherMeta dispatcherMeta, PromptBuilder promptBuilder) throws Exception {
245         context.addInHeader(
246                 context.style.italic().bold().foreground(Colors.rgbColor("yellow")),
247                 "Configure " + dispatcherMeta.displayName());
248         context.addInHeader("");
249 
250         for (DispatcherMeta.Field field : dispatcherMeta.fields()) {
251             String fieldKey = field.getKey();
252             String fieldDescription = "Configure " + fieldKey + ": " + field.getDescription();
253             if (field.getOptions().isPresent()) {
254                 // list options
255                 ListPromptBuilder listPromptBuilder =
256                         promptBuilder.createListPrompt().name(fieldKey).message(fieldDescription);
257                 for (DispatcherMeta.Field option : field.getOptions().get()) {
258                     listPromptBuilder
259                             .newItem()
260                             .name(
261                                     option.getDefaultValue().isPresent()
262                                             ? option.getDefaultValue().get()
263                                             : option.getKey())
264                             .text(option.getDescription())
265                             .add();
266                 }
267                 listPromptBuilder.addPrompt();
268             } else if (field.getDefaultValue().isPresent()) {
269                 // input w/ def value
270                 promptBuilder
271                         .createInputPrompt()
272                         .name(fieldKey)
273                         .message(fieldDescription)
274                         .defaultValue(field.getDefaultValue().get())
275                         .addPrompt();
276             } else {
277                 // ? plain input?
278                 promptBuilder
279                         .createInputPrompt()
280                         .name(fieldKey)
281                         .message(fieldDescription)
282                         .addPrompt();
283             }
284         }
285         return promptBuilder;
286     }
287 }