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