| 14 | import okhttp3.Request; |
| 15 | |
| 16 | public class Auth { |
| 17 | |
| 18 | private static final Pattern DIGEST = Pattern.compile("(\\w+)=(?:\"([^\"]*)\"|([^,\\s\"]+))"); |
| 19 | |
| 20 | public static String basic(String userInfo) { |
| 21 | if (!userInfo.contains(":")) userInfo += ":"; |
| 22 | return "Basic " + Base64.encodeToString(userInfo.getBytes(StandardCharsets.UTF_8), Base64.NO_WRAP); |
| 23 | } |
| 24 | |
| 25 | public static String digest(String userInfo, String header, Request request) { |
| 26 | Map<String, String> params = parseDigest(header.substring(7)); |
| 27 | String[] credentials = userInfo.split(":", 2); |
| 28 | String username = credentials[0]; |
| 29 | String password = credentials.length > 1 ? credentials[1] : ""; |
| 30 | String realm = params.getOrDefault("realm", ""); |
| 31 | String nonce = params.getOrDefault("nonce", ""); |
| 32 | String opaque = params.get("opaque"); |
| 33 | String uri = digestUri(request); |
| 34 | String qop = selectQop(params.get("qop")); |
| 35 | String nc = "00000001"; |
| 36 | String cnonce = newCnonce(); |
| 37 | String ha1 = Util.md5(username + ":" + realm + ":" + password); |
| 38 | String ha2 = Util.md5(request.method() + ":" + uri); |
| 39 | String response = digestResponse(ha1, ha2, nonce, nc, cnonce, qop); |
| 40 | return buildHeader(username, realm, nonce, uri, nc, cnonce, qop, response, opaque); |
| 41 | } |
| 42 | |
| 43 | private static String digestUri(Request request) { |
| 44 | String query = request.url().encodedQuery(); |
| 45 | String path = request.url().encodedPath(); |
| 46 | return query != null ? path + "?" + query : path; |
| 47 | } |
| 48 | |
| 49 | private static String digestResponse(String ha1, String ha2, String nonce, String nc, String cnonce, String qop) { |
| 50 | return qop.isEmpty() ? Util.md5(ha1 + ":" + nonce + ":" + ha2) : Util.md5(ha1 + ":" + nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + ha2); |
| 51 | } |
| 52 | |
| 53 | private static String buildHeader(String username, String realm, String nonce, String uri, String nc, String cnonce, String qop, String response, String opaque) { |
| 54 | List<String> fields = new ArrayList<>(); |
| 55 | fields.add("username=\"" + username + "\""); |
| 56 | fields.add("realm=\"" + realm + "\""); |
| 57 | fields.add("nonce=\"" + nonce + "\""); |
| 58 | fields.add("uri=\"" + uri + "\""); |
| 59 | boolean hasQop = !qop.isEmpty(); |
| 60 | if (hasQop) fields.add("cnonce=\"" + cnonce + "\""); |
| 61 | if (hasQop) fields.add("nc=" + nc); |
| 62 | if (hasQop) fields.add("qop=" + qop); |
| 63 | fields.add("response=\"" + response + "\""); |
| 64 | if (opaque != null) fields.add("opaque=\"" + opaque + "\""); |
| 65 | return "Digest " + String.join(", ", fields); |
| 66 | } |
| 67 | |
| 68 | private static String newCnonce() { |
| 69 | return UUID.randomUUID().toString().replace("-", ""); |
| 70 | } |
| 71 | |
| 72 | private static String selectQop(String qop) { |
| 73 | if (qop == null || qop.isEmpty()) return ""; |
nothing calls this directly
no outgoing calls
no test coverage detected