Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

JGitUserInteraction.java 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. /*
  2. * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch>
  3. * and other copyright owners as documented in the project's IP log.
  4. *
  5. * This program and the accompanying materials are made available
  6. * under the terms of the Eclipse Distribution License v1.0 which
  7. * accompanies this distribution, is reproduced below, and is
  8. * available at http://www.eclipse.org/org/documents/edl-v10.php
  9. *
  10. * All rights reserved.
  11. *
  12. * Redistribution and use in source and binary forms, with or
  13. * without modification, are permitted provided that the following
  14. * conditions are met:
  15. *
  16. * - Redistributions of source code must retain the above copyright
  17. * notice, this list of conditions and the following disclaimer.
  18. *
  19. * - Redistributions in binary form must reproduce the above
  20. * copyright notice, this list of conditions and the following
  21. * disclaimer in the documentation and/or other materials provided
  22. * with the distribution.
  23. *
  24. * - Neither the name of the Eclipse Foundation, Inc. nor the
  25. * names of its contributors may be used to endorse or promote
  26. * products derived from this software without specific prior
  27. * written permission.
  28. *
  29. * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  30. * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  31. * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
  32. * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  33. * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
  34. * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  35. * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  36. * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  37. * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  38. * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
  39. * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  40. * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  41. * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  42. */
  43. package org.eclipse.jgit.internal.transport.sshd;
  44. import java.net.InetSocketAddress;
  45. import java.util.ArrayList;
  46. import java.util.List;
  47. import java.util.Map;
  48. import java.util.concurrent.ConcurrentHashMap;
  49. import org.apache.sshd.client.auth.keyboard.UserInteraction;
  50. import org.apache.sshd.client.session.ClientSession;
  51. import org.apache.sshd.common.session.Session;
  52. import org.apache.sshd.common.session.SessionListener;
  53. import org.eclipse.jgit.transport.CredentialItem;
  54. import org.eclipse.jgit.transport.CredentialsProvider;
  55. import org.eclipse.jgit.transport.SshConstants;
  56. import org.eclipse.jgit.transport.URIish;
  57. /**
  58. * A {@link UserInteraction} callback implementation based on a
  59. * {@link CredentialsProvider}.
  60. */
  61. public class JGitUserInteraction implements UserInteraction {
  62. private final CredentialsProvider provider;
  63. /**
  64. * We need to reset the JGit credentials provider if we have repeated
  65. * attempts.
  66. */
  67. private final Map<Session, SessionListener> ongoing = new ConcurrentHashMap<>();
  68. /**
  69. * Creates a new {@link JGitUserInteraction} for interactive password input
  70. * based on the given {@link CredentialsProvider}.
  71. *
  72. * @param provider
  73. * to use
  74. */
  75. public JGitUserInteraction(CredentialsProvider provider) {
  76. this.provider = provider;
  77. }
  78. @Override
  79. public boolean isInteractionAllowed(ClientSession session) {
  80. return provider != null && provider.isInteractive();
  81. }
  82. @Override
  83. public String[] interactive(ClientSession session, String name,
  84. String instruction, String lang, String[] prompt, boolean[] echo) {
  85. // This is keyboard-interactive or password authentication
  86. List<CredentialItem> items = new ArrayList<>();
  87. int numberOfHiddenInputs = 0;
  88. for (int i = 0; i < prompt.length; i++) {
  89. boolean hidden = i < echo.length && !echo[i];
  90. if (hidden) {
  91. numberOfHiddenInputs++;
  92. }
  93. }
  94. // RFC 4256 (SSH_MSG_USERAUTH_INFO_REQUEST) says: "The language tag is
  95. // deprecated and SHOULD be the empty string." and "[If there are no
  96. // prompts] the client SHOULD still display the name and instruction
  97. // fields" and "[The] client SHOULD print the name and instruction (if
  98. // non-empty)"
  99. if (name != null && !name.isEmpty()) {
  100. items.add(new CredentialItem.InformationalMessage(name));
  101. }
  102. if (instruction != null && !instruction.isEmpty()) {
  103. items.add(new CredentialItem.InformationalMessage(instruction));
  104. }
  105. for (int i = 0; i < prompt.length; i++) {
  106. boolean hidden = i < echo.length && !echo[i];
  107. if (hidden && numberOfHiddenInputs == 1) {
  108. // We need to somehow trigger storing the password in the
  109. // Eclipse secure storage in EGit. Currently, this is done only
  110. // for password fields.
  111. items.add(new CredentialItem.Password());
  112. // TODO Possibly change EGit to store all hidden strings
  113. // (keyed by the URI and the prompt?) so that we don't have to
  114. // use this kludge here.
  115. } else {
  116. items.add(new CredentialItem.StringType(prompt[i], hidden));
  117. }
  118. }
  119. if (items.isEmpty()) {
  120. // Huh? No info, no prompts?
  121. return prompt; // Is known to have length zero here
  122. }
  123. URIish uri = toURI(session.getUsername(),
  124. (InetSocketAddress) session.getConnectAddress());
  125. // Reset the provider for this URI if it's not the first attempt and we
  126. // have hidden inputs. Otherwise add a session listener that will remove
  127. // itself once authenticated.
  128. if (numberOfHiddenInputs > 0) {
  129. SessionListener listener = ongoing.get(session);
  130. if (listener != null) {
  131. provider.reset(uri);
  132. } else {
  133. listener = new SessionAuthMarker(ongoing);
  134. ongoing.put(session, listener);
  135. session.addSessionListener(listener);
  136. }
  137. }
  138. if (provider.get(uri, items)) {
  139. return items.stream().map(i -> {
  140. if (i instanceof CredentialItem.Password) {
  141. return new String(((CredentialItem.Password) i).getValue());
  142. } else if (i instanceof CredentialItem.StringType) {
  143. return ((CredentialItem.StringType) i).getValue();
  144. }
  145. return null;
  146. }).filter(s -> s != null).toArray(String[]::new);
  147. }
  148. // TODO What to throw to abort the connection/authentication process?
  149. // In UserAuthKeyboardInteractive.getUserResponses() it's clear that
  150. // returning null is valid and signifies "an error"; we'll try the
  151. // next authentication method. But if the user explicitly canceled,
  152. // then we don't want to try the next methods...
  153. //
  154. // Probably not a serious issue with the typical order of public-key,
  155. // keyboard-interactive, password.
  156. return null;
  157. }
  158. @Override
  159. public String getUpdatedPassword(ClientSession session, String prompt,
  160. String lang) {
  161. // TODO Implement password update in password authentication?
  162. return null;
  163. }
  164. /**
  165. * Creates a {@link URIish} from the given remote address and user name.
  166. *
  167. * @param userName
  168. * for the uri
  169. * @param remote
  170. * address of the remote host
  171. * @return the uri, with {@link SshConstants#SSH_SCHEME} as scheme
  172. */
  173. public static URIish toURI(String userName, InetSocketAddress remote) {
  174. String host = remote.getHostString();
  175. int port = remote.getPort();
  176. return new URIish() //
  177. .setScheme(SshConstants.SSH_SCHEME) //
  178. .setHost(host) //
  179. .setPort(port) //
  180. .setUser(userName);
  181. }
  182. /**
  183. * A {@link SessionListener} that removes itself from the session when
  184. * authentication is done or the session is closed.
  185. */
  186. private static class SessionAuthMarker implements SessionListener {
  187. private final Map<Session, SessionListener> registered;
  188. public SessionAuthMarker(Map<Session, SessionListener> registered) {
  189. this.registered = registered;
  190. }
  191. @Override
  192. public void sessionEvent(Session session, SessionListener.Event event) {
  193. if (event == SessionListener.Event.Authenticated) {
  194. session.removeSessionListener(this);
  195. registered.remove(session, this);
  196. }
  197. }
  198. @Override
  199. public void sessionClosed(Session session) {
  200. session.removeSessionListener(this);
  201. registered.remove(session, this);
  202. }
  203. }
  204. }