}\r
\r
public static enum AuthenticationType {\r
- PUBLIC_KEY, CREDENTIALS, COOKIE, CERTIFICATE, CONTAINER;\r
+ PUBLIC_KEY, CREDENTIALS, COOKIE, CERTIFICATE, CONTAINER, GITHUB_OAUTH;\r
\r
public boolean isStandard() {\r
return ordinal() <= COOKIE.ordinal();\r
--- /dev/null
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.gitblit.auth.github;
+
+import com.google.common.collect.Iterators;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+
+class AuthenticatedHttpRequest extends HttpServletRequestWrapper {
+ private Map<String, String> headers = new HashMap<>();
+
+ AuthenticatedHttpRequest(HttpServletRequest request,
+ String key, String value) {
+ super(request);
+ headers.put(key, value);
+ }
+
+ @Override
+ public Enumeration<String> getHeaderNames() {
+ return Iterators.asEnumeration(
+ Iterators.concat(Iterators.forEnumeration(super.getHeaderNames()),
+ headers.keySet().iterator()));
+ }
+
+ @Override
+ public String getHeader(String name) {
+ String headerValue = headers.get(name);
+ if (headerValue != null) {
+ return headerValue;
+ } else {
+ return super.getHeader(name);
+ }
+ }
+}
--- /dev/null
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.gitblit.auth.github;
+
+import com.google.inject.Inject;
+import com.google.inject.servlet.SessionScoped;
+
+import org.apache.http.HttpStatus;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+@SessionScoped
+class GitHubLogin {
+ private static final Logger log = LoggerFactory.getLogger(GitHubLogin.class);
+
+ private final OAuthProtocol oauth;
+ private String token;
+ private String user;
+
+ @Inject
+ GitHubLogin(final OAuthProtocol oauth) {
+ this.oauth = oauth;
+ }
+
+ boolean isLoggedIn() {
+ return token != null && user != null;
+ }
+
+ boolean login(HttpServletRequest request, HttpServletResponse response)
+ throws IOException {
+ if (isLoggedIn()) {
+ return true;
+ }
+
+ log.debug("Login " + this);
+
+ if (OAuthProtocol.isOAuthFinal(request)) {
+ String redirectUrl = oauth.getTargetUrl(request);
+ if (redirectUrl == null) {
+ response.sendError(HttpServletResponse.SC_NOT_FOUND);
+ return false;
+ }
+
+ log.debug("Login-Retrieve-User " + this);
+ retrieveUser(oauth.loginPhase2(request, response));
+ if (isLoggedIn()) {
+ log.debug("Login-SUCCESS " + this);
+ response.sendRedirect(redirectUrl);
+ return true;
+ } else {
+ response.sendError(HttpStatus.SC_UNAUTHORIZED);
+ return false;
+ }
+ } else {
+ log.debug("Login-PHASE1 " + this);
+ oauth.loginPhase1(request, response);
+ return false;
+ }
+ }
+
+ void logout() {
+ token = null;
+ user = null;
+ }
+
+ boolean isLoginRequest(HttpServletRequest httpRequest) {
+ return oauth.isOAuthRequest(httpRequest);
+ }
+
+ String getUsername() {
+ return user;
+ }
+
+ @Override
+ public String toString() {
+ return "GitHubLogin [token=" + token + ", user=" + user + "]";
+ }
+
+ private void retrieveUser(String authToken) throws IOException {
+ this.token = authToken;
+ this.user = oauth.retrieveUser(authToken);
+ }
+}
--- /dev/null
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.gitblit.auth.github;
+
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import com.gitblit.IStoredSettings;
+import com.google.common.base.CharMatcher;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+@Singleton
+class GitHubOAuthConfig {
+ private static final String GITHUB_URL = "https://github.com";
+ private static final String GITHUB_API_URL = "https://api.github.com";
+ protected static final String CONF_SECTION = "github";
+ private static final String GITHUB_OAUTH_AUTHORIZE = "/login/oauth/authorize";
+ public static final String GITHUB_OAUTH_ACCESS_TOKEN =
+ "/login/oauth/access_token";
+ private static final String GITHUB_GET_USER = "/user";
+ //private static final String GITHUB_OAUTH_FINAL = "/oauth";
+ static final String GITHUB_LOGIN = "/login";
+
+ private final String gitHubUrl;
+ private final String gitHubApiUrl;
+ final String gitHubUserUrl;
+ final String gitHubClientId;
+ final String gitHubClientSecret;
+ final String httpHeader;
+ final String gitHubOAuthUrl;
+ final String oAuthFinalRedirectUrl;
+ final String gitHubOAuthAccessTokenUrl;
+ final boolean autoLogin;
+
+ @Inject
+ GitHubOAuthConfig(IStoredSettings settings)
+ throws MalformedURLException {
+ httpHeader = settings.getString("httpHeader", "GITHUB_USER");
+ gitHubUrl = GITHUB_URL;
+ gitHubApiUrl = GITHUB_API_URL;
+ gitHubClientId = settings.getString("clientId", "4711");
+ gitHubClientSecret = settings.getString("clientSecret", "4712");
+
+ gitHubOAuthUrl = getUrl(gitHubUrl, GITHUB_OAUTH_AUTHORIZE);
+ gitHubOAuthAccessTokenUrl = getUrl(gitHubUrl, GITHUB_OAUTH_ACCESS_TOKEN);
+ gitHubUserUrl = getUrl(gitHubApiUrl, GITHUB_GET_USER);
+ oAuthFinalRedirectUrl = settings.getString("canonicalWebUrl",
+ "http://locahost:8080");
+ autoLogin = false;
+ }
+
+ private static String trimTrailingSlash(String url) {
+ return CharMatcher.is('/').trimTrailingFrom(url);
+ }
+
+ private String getUrl(String baseUrl, String path)
+ throws MalformedURLException {
+ if (baseUrl.indexOf("://") > 0) {
+ return new URL(new URL(baseUrl), path).toExternalForm();
+ } else {
+ return baseUrl + trimTrailingSlash(baseUrl) + "/"
+ + CharMatcher.is('/').trimLeadingFrom(path);
+ }
+ }
+}
--- /dev/null
+// Copyright (C) 2014 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.gitblit.auth.github;
+
+import com.google.inject.servlet.ServletModule;
+
+import org.apache.http.client.HttpClient;
+
+/** Servlets and support related to GitHub OAuth authentication. */
+public class HttpGitHubOAuthModule extends ServletModule {
+
+ @Override
+ protected void configureServlets() {
+ filter("/").through(OAuthWebFilter.class);
+ filter("/login").through(OAuthWebFilter.class);
+ filter("/oauth").through(OAuthWebFilter.class);
+
+ bind(HttpClient.class).toProvider(PooledHttpClientProvider.class);
+ }
+}
--- /dev/null
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.gitblit.auth.github;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Strings;
+import com.google.gson.Gson;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.inject.Inject;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.http.HttpResponse;
+import org.apache.http.NameValuePair;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.entity.UrlEncodedFormEntity;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.message.BasicNameValuePair;
+import org.apache.http.util.EntityUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UnsupportedEncodingException;
+import java.net.HttpURLConnection;
+import java.net.URLEncoder;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.servlet.ServletRequest;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+class OAuthProtocol {
+ private static final String ME_SEPARATOR = ",";
+ private static final Logger log = LoggerFactory
+ .getLogger(OAuthProtocol.class);
+
+ private final GitHubOAuthConfig config;
+ private final HttpClient http;
+ private final Gson gson;
+ private final String state;
+
+ @Inject
+ OAuthProtocol(GitHubOAuthConfig config, HttpClient http,
+ Gson gson) {
+ this.config = config;
+ this.http = http;
+ this.gson = gson;
+ this.state = generateRandomState();
+ }
+
+ void loginPhase1(HttpServletRequest request,
+ HttpServletResponse response) throws IOException {
+ log.debug("Initiating GitHub Login for ClientId=" + config.gitHubClientId);
+ response.sendRedirect(String.format(
+ "%s?client_id=%s&redirect_uri=%s&state=%s%s", config.gitHubOAuthUrl,
+ config.gitHubClientId, getURLEncoded(config.oAuthFinalRedirectUrl),
+ me(), getURLEncoded(request.getRequestURI().toString())));
+ }
+
+ String loginPhase2(HttpServletRequest request,
+ HttpServletResponse response) throws IOException {
+ HttpPost post = new HttpPost(config.gitHubOAuthAccessTokenUrl);
+ post.setHeader("Accept", "application/json");
+ List<NameValuePair> nvps = new ArrayList<>(3);
+ nvps.add(new BasicNameValuePair("client_id", config.gitHubClientId));
+ nvps.add(new BasicNameValuePair("client_secret",
+ config.gitHubClientSecret));
+ nvps.add(new BasicNameValuePair("code", request.getParameter("code")));
+ post.setEntity(new UrlEncodedFormEntity(nvps));
+
+ try {
+ HttpResponse postResponse = http.execute(post);
+ if (postResponse.getStatusLine().getStatusCode() !=
+ HttpURLConnection.HTTP_OK) {
+ log.error("POST " + config.gitHubOAuthAccessTokenUrl
+ + " request for access token failed with status "
+ + postResponse.getStatusLine());
+ response.sendError(HttpURLConnection.HTTP_UNAUTHORIZED,
+ "Request for access token not authorised");
+ EntityUtils.consume(postResponse.getEntity());
+ return null;
+ }
+
+ return getAccessToken(getAccessTokenJson(postResponse));
+ } catch (IOException e) {
+ log.error("POST " + config.gitHubOAuthAccessTokenUrl
+ + " request for access token failed", e);
+ response.sendError(HttpServletResponse.SC_UNAUTHORIZED,
+ "Request for access token not authorised");
+ return null;
+ }
+ }
+
+ String retrieveUser(String authToken) throws IOException {
+ HttpGet get = new HttpGet(config.gitHubUserUrl);
+ get.setHeader("Authorization", String.format("token %s", authToken));
+ try {
+ return getLogin(getUserJson(httpGetGitHubUserInfo(get)));
+ } catch (IOException e) {
+ log.error("GET {} with authToken {} request failed",
+ config.gitHubUserUrl, config.gitHubOAuthAccessTokenUrl, e);
+ return null;
+ }
+ }
+
+ private InputStream httpGetGitHubUserInfo(HttpGet get) throws IOException,
+ ClientProtocolException {
+ HttpResponse resp = http.execute(get);
+ int statusCode = resp.getStatusLine().getStatusCode();
+ if (statusCode == HttpServletResponse.SC_OK) {
+ return resp.getEntity().getContent();
+ } else {
+ throw new IOException(String.format(
+ "Invalid HTTP status code %s returned from %s", statusCode,
+ get.getURI()));
+ }
+ }
+
+ private String getAccessToken(JsonElement accessTokenJson)
+ throws IOException {
+ JsonElement accessTokenString =
+ accessTokenJson.getAsJsonObject().get("access_token");
+ if (accessTokenString != null) {
+ return accessTokenString.getAsString();
+ } else {
+ throw new IOException(String.format(
+ "Invalid JSON '%s': cannot find access_token field",
+ accessTokenJson));
+ }
+ }
+
+ private JsonObject getAccessTokenJson(HttpResponse postResponse)
+ throws UnsupportedEncodingException, IOException {
+ JsonElement accessTokenJson =
+ gson.fromJson(new InputStreamReader(postResponse.getEntity()
+ .getContent(), Charsets.UTF_8), JsonElement.class);
+ if (accessTokenJson.isJsonObject()) {
+ return accessTokenJson.getAsJsonObject();
+ } else {
+ throw new IOException(String.format(
+ "Invalid JSON '%s': not a JSON Object"));
+ }
+ }
+
+ boolean isOAuthRequest(HttpServletRequest httpRequest) {
+ return OAuthProtocol.isGerritLogin(httpRequest)
+ || OAuthProtocol.isOAuthFinal(httpRequest);
+ }
+
+ String getTargetUrl(ServletRequest request) {
+ String requestState = state(request);
+ int meEnd = requestState.indexOf(ME_SEPARATOR);
+ if (meEnd >= 0 && requestState.subSequence(0, meEnd).equals(state)) {
+ return requestState.substring(meEnd + 1);
+ } else {
+ log.warn("Illegal request state '" + requestState + "' on OAuthProtocol "
+ + this);
+ return null;
+ }
+ }
+
+ private String me() {
+ return state + ME_SEPARATOR;
+ }
+
+ private JsonObject getUserJson(InputStream userContentStream)
+ throws IOException {
+ JsonElement userJson =
+ gson.fromJson(new InputStreamReader(userContentStream,
+ Charsets.UTF_8), JsonElement.class);
+ if (userJson.isJsonObject()) {
+ return userJson.getAsJsonObject();
+ } else {
+ throw new IOException(String.format(
+ "Invalid JSON '%s': not a JSON Object", userJson));
+ }
+ }
+
+ static boolean isOAuthFinal(HttpServletRequest request) {
+ return Strings.emptyToNull(request.getParameter("code")) != null;
+ }
+
+ static boolean isGerritLogin(HttpServletRequest request) {
+ return request.getRequestURI().indexOf(
+ GitHubOAuthConfig.GITHUB_LOGIN) >= 0;
+ }
+
+ private static String getLogin(JsonElement userJson) throws IOException {
+ JsonElement userString = userJson.getAsJsonObject().get("login");
+ if (userString != null) {
+ return userString.getAsString();
+ } else {
+ throw new IOException(String.format(
+ "Invalid JSON '%s': cannot find login field", userJson));
+ }
+ }
+
+ private static String generateRandomState() {
+ byte[] randomState = new byte[32];
+ new SecureRandom().nextBytes(randomState);
+ return Base64.encodeBase64URLSafeString(randomState);
+ }
+
+ private static String getURLEncoded(String url) {
+ try {
+ return URLEncoder.encode(url, Charsets.UTF_8.name());
+ } catch (UnsupportedEncodingException e) {
+ // UTF-8 is hardcoded, cannot fail
+ return null;
+ }
+ }
+
+ private static String state(ServletRequest request) {
+ return Strings.nullToEmpty(request.getParameter("state"));
+ }
+
+ @Override
+ public String toString() {
+ return "OAuthProtocol/" + state;
+ }
+}
--- /dev/null
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package com.gitblit.auth.github;
+
+
+import com.gitblit.manager.IAuthenticationManager;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.FilterChain;
+import javax.servlet.FilterConfig;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+@Singleton
+class OAuthWebFilter implements Filter {
+ private static final Logger log = LoggerFactory
+ .getLogger(OAuthWebFilter.class);
+
+ private final GitHubOAuthConfig config;
+ private final Provider<GitHubLogin> loginProvider;
+ private final IAuthenticationManager authenticationManager;
+
+ @Inject
+ OAuthWebFilter(GitHubOAuthConfig config,
+ Provider<GitHubLogin> loginProvider,
+ IAuthenticationManager authenticationManager) {
+ this.config = config;
+ this.loginProvider = loginProvider;
+ this.authenticationManager = authenticationManager;
+ }
+
+ @Override
+ public void init(FilterConfig filterConfig) throws ServletException {
+ }
+
+ @Override
+ public void doFilter(ServletRequest request, ServletResponse response,
+ FilterChain chain) throws IOException, ServletException {
+ HttpSession httpSession = ((HttpServletRequest) request).getSession(false);
+ // TODO(davido): FixMe: How to do that on gitblit?
+ //if (authenticationManager.isIdentifiedUser()) {
+ if (false) {
+ if (httpSession != null) {
+ httpSession.invalidate();
+ }
+ chain.doFilter(request, response);
+ return;
+ }
+
+ HttpServletRequest httpRequest = (HttpServletRequest) request;
+ HttpServletResponse httpResponse = (HttpServletResponse) response;
+ log.debug("OAuthWebFilter({})", httpRequest.getRequestURL());
+
+ GitHubLogin ghLogin = loginProvider.get();
+
+ if (ghLogin.isLoginRequest(httpRequest) && !ghLogin.isLoggedIn()) {
+ ghLogin.login(httpRequest, httpResponse);
+ } else if (config.autoLogin && !ghLogin.isLoggedIn()) {
+ httpResponse.sendRedirect("/login");
+ } else {
+ if (ghLogin.isLoggedIn()) {
+ httpRequest =
+ new AuthenticatedHttpRequest(httpRequest, config.httpHeader,
+ ghLogin.getUsername());
+ }
+ chain.doFilter(httpRequest, response);
+ }
+ }
+
+ @Override
+ public void destroy() {
+ }
+}
--- /dev/null
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.gitblit.auth.github;
+
+import org.apache.http.client.HttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+@Singleton
+class PooledHttpClientProvider implements Provider<HttpClient> {
+
+ @Override
+ public HttpClient get() {
+ // TODO(davido): handle proxy
+ // TODO(davido): externalize MaxConnPerRoute && MaxConnTotal values
+ return HttpClientBuilder
+ .create()
+ .setMaxConnPerRoute(100)
+ .setMaxConnTotal(1024)
+ .build();
+ }
+}
import java.util.Map;
import com.gitblit.Constants;
+import com.gitblit.auth.github.HttpGitHubOAuthModule;
import com.gitblit.servlet.BranchGraphServlet;
import com.gitblit.servlet.DownloadZipFilter;
import com.gitblit.servlet.DownloadZipServlet;
params.put(GitblitWicketFilter.FILTER_MAPPING_PARAM, ALL);
params.put(GitblitWicketFilter.IGNORE_PATHS_PARAM, toIgnore);
filter(ALL).through(GitblitWicketFilter.class, params);
+
+ //if (AUTH_METHOD == GITHUB_OAUTH) {
+ if (true) {
+ install(new HttpGitHubOAuthModule());
+ }
}
private String fuzzy(String path) {
\r
Gitblit supports additional authentication mechanisms aside from it's internal one.\r
\r
+* GitHub OAuth\r
* LDAP authentication\r
* Windows authentication\r
* PAM authentication\r
* Salesforce.com authentication\r
* Servlet container authentication\r
\r
+### GitHub OAuth\r
+*SINCE 1.7.0\r
+\r
+OAuth2 is a protocol that lets external apps request authorization to private\r
+details in a user’s GitHub account without getting their password. This is\r
+preferred over Basic Authentication because tokens can be limited to specific\r
+types of data, and can be revoked by users at any time.\r
++\r
+Site owners have to register their application before getting started. For\r
+more information see\r
+https://github.com/settings/applications/new[github-register-application].\r
+A registered OAuth application is assigned a unique `Client ID` and `Client\r
+Secret`. The `Client Secret` should never be shared.\r
+\r
+[[github.url]]github.url::\r
+\r
+GitHub URL.\r
+\r
+Default is `https://github.com`.\r
+\r
+[[github.apiUrl]]github.apiUrl::\r
+\r
+GitHub API URL.\r
+\r
+Default is `https://api.github.com`.\r
+\r
+[[github.clientId]]github.clientId::\r
+\r
+The `Client ID`, that was received from GitHub when the application was\r
+registered. Required.\r
+\r
+[[github.clientSecret]]github.clientSecret::\r
+\r
+The `Client Secret`, that was received from GitHub when the application was\r
+registered. Required.\r
+\r
+\r
### LDAP Authentication\r
*SINCE 1.0.0*\r
\r