您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

JGitUserInteraction.java 6.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186
  1. /*
  2. * Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * https://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.internal.transport.sshd;
  11. import java.net.InetSocketAddress;
  12. import java.util.ArrayList;
  13. import java.util.List;
  14. import java.util.Map;
  15. import java.util.concurrent.ConcurrentHashMap;
  16. import org.apache.sshd.client.auth.keyboard.UserInteraction;
  17. import org.apache.sshd.client.session.ClientSession;
  18. import org.apache.sshd.common.session.Session;
  19. import org.apache.sshd.common.session.SessionListener;
  20. import org.eclipse.jgit.transport.CredentialItem;
  21. import org.eclipse.jgit.transport.CredentialsProvider;
  22. import org.eclipse.jgit.transport.SshConstants;
  23. import org.eclipse.jgit.transport.URIish;
  24. /**
  25. * A {@link UserInteraction} callback implementation based on a
  26. * {@link CredentialsProvider}.
  27. */
  28. public class JGitUserInteraction implements UserInteraction {
  29. private final CredentialsProvider provider;
  30. /**
  31. * We need to reset the JGit credentials provider if we have repeated
  32. * attempts.
  33. */
  34. private final Map<Session, SessionListener> ongoing = new ConcurrentHashMap<>();
  35. /**
  36. * Creates a new {@link JGitUserInteraction} for interactive password input
  37. * based on the given {@link CredentialsProvider}.
  38. *
  39. * @param provider
  40. * to use
  41. */
  42. public JGitUserInteraction(CredentialsProvider provider) {
  43. this.provider = provider;
  44. }
  45. @Override
  46. public boolean isInteractionAllowed(ClientSession session) {
  47. return provider != null && provider.isInteractive();
  48. }
  49. @Override
  50. public String[] interactive(ClientSession session, String name,
  51. String instruction, String lang, String[] prompt, boolean[] echo) {
  52. // This is keyboard-interactive or password authentication
  53. List<CredentialItem> items = new ArrayList<>();
  54. int numberOfHiddenInputs = 0;
  55. for (int i = 0; i < prompt.length; i++) {
  56. boolean hidden = i < echo.length && !echo[i];
  57. if (hidden) {
  58. numberOfHiddenInputs++;
  59. }
  60. }
  61. // RFC 4256 (SSH_MSG_USERAUTH_INFO_REQUEST) says: "The language tag is
  62. // deprecated and SHOULD be the empty string." and "[If there are no
  63. // prompts] the client SHOULD still display the name and instruction
  64. // fields" and "[The] client SHOULD print the name and instruction (if
  65. // non-empty)"
  66. if (name != null && !name.isEmpty()) {
  67. items.add(new CredentialItem.InformationalMessage(name));
  68. }
  69. if (instruction != null && !instruction.isEmpty()) {
  70. items.add(new CredentialItem.InformationalMessage(instruction));
  71. }
  72. for (int i = 0; i < prompt.length; i++) {
  73. boolean hidden = i < echo.length && !echo[i];
  74. if (hidden && numberOfHiddenInputs == 1) {
  75. // We need to somehow trigger storing the password in the
  76. // Eclipse secure storage in EGit. Currently, this is done only
  77. // for password fields.
  78. items.add(new CredentialItem.Password());
  79. // TODO Possibly change EGit to store all hidden strings
  80. // (keyed by the URI and the prompt?) so that we don't have to
  81. // use this kludge here.
  82. } else {
  83. items.add(new CredentialItem.StringType(prompt[i], hidden));
  84. }
  85. }
  86. if (items.isEmpty()) {
  87. // Huh? No info, no prompts?
  88. return prompt; // Is known to have length zero here
  89. }
  90. URIish uri = toURI(session.getUsername(),
  91. (InetSocketAddress) session.getConnectAddress());
  92. // Reset the provider for this URI if it's not the first attempt and we
  93. // have hidden inputs. Otherwise add a session listener that will remove
  94. // itself once authenticated.
  95. if (numberOfHiddenInputs > 0) {
  96. SessionListener listener = ongoing.get(session);
  97. if (listener != null) {
  98. provider.reset(uri);
  99. } else {
  100. listener = new SessionAuthMarker(ongoing);
  101. ongoing.put(session, listener);
  102. session.addSessionListener(listener);
  103. }
  104. }
  105. if (provider.get(uri, items)) {
  106. return items.stream().map(i -> {
  107. if (i instanceof CredentialItem.Password) {
  108. return new String(((CredentialItem.Password) i).getValue());
  109. } else if (i instanceof CredentialItem.StringType) {
  110. return ((CredentialItem.StringType) i).getValue();
  111. }
  112. return null;
  113. }).filter(s -> s != null).toArray(String[]::new);
  114. }
  115. // TODO What to throw to abort the connection/authentication process?
  116. // In UserAuthKeyboardInteractive.getUserResponses() it's clear that
  117. // returning null is valid and signifies "an error"; we'll try the
  118. // next authentication method. But if the user explicitly canceled,
  119. // then we don't want to try the next methods...
  120. //
  121. // Probably not a serious issue with the typical order of public-key,
  122. // keyboard-interactive, password.
  123. return null;
  124. }
  125. @Override
  126. public String getUpdatedPassword(ClientSession session, String prompt,
  127. String lang) {
  128. // TODO Implement password update in password authentication?
  129. return null;
  130. }
  131. /**
  132. * Creates a {@link URIish} from the given remote address and user name.
  133. *
  134. * @param userName
  135. * for the uri
  136. * @param remote
  137. * address of the remote host
  138. * @return the uri, with {@link SshConstants#SSH_SCHEME} as scheme
  139. */
  140. public static URIish toURI(String userName, InetSocketAddress remote) {
  141. String host = remote.getHostString();
  142. int port = remote.getPort();
  143. return new URIish() //
  144. .setScheme(SshConstants.SSH_SCHEME) //
  145. .setHost(host) //
  146. .setPort(port) //
  147. .setUser(userName);
  148. }
  149. /**
  150. * A {@link SessionListener} that removes itself from the session when
  151. * authentication is done or the session is closed.
  152. */
  153. private static class SessionAuthMarker implements SessionListener {
  154. private final Map<Session, SessionListener> registered;
  155. public SessionAuthMarker(Map<Session, SessionListener> registered) {
  156. this.registered = registered;
  157. }
  158. @Override
  159. public void sessionEvent(Session session, SessionListener.Event event) {
  160. if (event == SessionListener.Event.Authenticated) {
  161. session.removeSessionListener(this);
  162. registered.remove(session, this);
  163. }
  164. }
  165. @Override
  166. public void sessionClosed(Session session) {
  167. session.removeSessionListener(this);
  168. registered.remove(session, this);
  169. }
  170. }
  171. }