Skip to content

Commit

Permalink
Add a login form and a logout button to the WebGUI
Browse files Browse the repository at this point in the history
  • Loading branch information
whisperity committed Jan 31, 2018
1 parent 6cd1241 commit ae3405d
Show file tree
Hide file tree
Showing 11 changed files with 410 additions and 42 deletions.
17 changes: 9 additions & 8 deletions api/v6/authentication.thrift
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ namespace py Authentication_v6
namespace js codeCheckerAuthentication_v6

struct HandshakeInformation {
1: bool requiresAuthentication, // true if the server has a privileged zone --- the state of having a valid access is not considered here
2: bool sessionStillActive // whether the session in which the HandshakeInformation is returned is a valid one
1: bool requiresAuthentication, // True if the server has a privileged zone.
2: bool sessionStillActive // Whether the session in which the HandshakeInformation is returned is a live one
}

struct AuthorisationList {
Expand All @@ -36,24 +36,25 @@ service codeCheckerAuthentication {
throws (1: shared.RequestFailed requestError),

// ============= Authentication and session handling =============
// get basic authentication information from the server
// Get basic authentication information from the server.
HandshakeInformation getAuthParameters(),


// retrieves a list of accepted authentication methods from the server
// Retrieves a list of accepted authentication methods from the server.
list<string> getAcceptedAuthMethods(),

// handles creating a session token for the user
// Handles creating a session token for the user.
string performLogin(1: string authMethod,
2: string authString)
throws (1: shared.RequestFailed requestError),

// performs logout action for the user (must be called from the corresponding valid session)
// Performs logout action for the user. Must be called from the
// corresponding valid session which is to be destroyed.
bool destroySession()
throws (1: shared.RequestFailed requestError),

// returns currently logged in user within the active session
// returns empty string if the session is not active
// Returns the currently logged in user within the active session, or empty
// string if no authenticated session is active.
string getLoggedInUser()
throws (1: shared.RequestFailed requestError),

Expand Down
2 changes: 1 addition & 1 deletion libcodechecker/libclient/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ def handle_auth(protocol, host, port, username, login=False):
pwd = saved_auth.split(":")[1]
else:
LOG.info("Logging in using credentials from command line...")
pwd = getpass.getpass("Please provide password for user '{0}'"
pwd = getpass.getpass("Please provide password for user '{0}': "
.format(username))

LOG.debug("Trying to login as {0} to {1}:{2}"
Expand Down
20 changes: 18 additions & 2 deletions libcodechecker/server/routing.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@

# A list of top-level path elements under the webserver root
# which should not be considered as a product route.
NON_PRODUCT_ENDPOINTS = ['products.html',
'index.html',
NON_PRODUCT_ENDPOINTS = ['index.html',
'login.html',
'products.html',
'fonts',
'images',
'scripts',
Expand All @@ -31,6 +32,13 @@
]


# A list of top-level path elements under the webserver root which should
# be protected by authentication requirement when accessing the server.
PROTECTED_ENTRY_POINTS = ['', # Empty string in a request is 'index.html'.
'index.html',
'products.html']


def is_valid_product_endpoint(uripart):
"""
Returns whether or not the given URI part is to be considered a valid
Expand Down Expand Up @@ -122,3 +130,11 @@ def split_client_POST_request(path):
remainder = split_path[2]

return None, version_tag, remainder


def is_protected_GET_entrypoint(path):
"""
Returns if the given GET request's PATH enters the server through an
entry point which is considered protected by authentication requirements.
"""
return path in PROTECTED_ENTRY_POINTS
38 changes: 13 additions & 25 deletions libcodechecker/server/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,21 +108,6 @@ def __check_auth_in_request(self):
success = self.server.manager.get_session(values[1],
True)

if success is None:
# Session cookie was invalid (or not found...)
# Attempt to see if the browser has sent us
# an authentication request.
authHeader = self.headers.getheader("Authorization")
if authHeader is not None and authHeader.startswith("Basic "):
authString = base64.decodestring(
self.headers.getheader("Authorization").
replace("Basic ", ""))

session = self.server.manager.create_or_get_session(
authString)
if session:
return session

# Else, access is still not granted.
if success is None:
LOG.debug(client_host + ":" + str(client_port) +
Expand All @@ -147,7 +132,7 @@ def end_headers(self):

def do_GET(self):
"""
Handles the webbrowser access (GET requests).
Handles the browser access (GET requests).
"""

auth_session = self.__check_auth_in_request()
Expand All @@ -157,21 +142,24 @@ def do_GET(self):
auth_session.user if auth_session else 'Anonymous',
self.path))

if not self.path.startswith('/login.html') and \
self.server.manager.is_enabled and not auth_session:
returnto = '?returnto=' + self.path.ltrim('/') \
if auth_session is not None:
self.auth_token = auth_session.token

product_endpoint, path = routing.split_client_GET_request(self.path)

if self.server.manager.is_enabled and not auth_session \
and routing.is_protected_GET_entrypoint(path):
# If necessary, prompt the user for authentication.
returnto = '#returnto=' + urllib.quote_plus(self.path.lstrip('/'))\
if self.path != '/' else ''

self.send_response(307) # 307 Temporary Redirect
self.send_header("Location", '/login.html' + returnto)
self.send_header('Location', '/login.html' + returnto)
self.send_header('Connection', 'close')
self.end_headers()
self.wfile.write('')
return

if auth_session is not None:
self.auth_token = auth_session.token

product_endpoint, path = routing.split_client_GET_request(self.path)

if product_endpoint is not None and product_endpoint != '':
# Route the user if there is a product endpoint in the request.

Expand Down
2 changes: 1 addition & 1 deletion libcodechecker/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

# The name of the cookie which contains the user's authentication session's
# token.
SESSION_COOKIE_NAME = "__ccPrivilegedAccessToken"
SESSION_COOKIE_NAME = '__ccPrivilegedAccessToken'

# The newest supported minor version (value) for each supported major version
# (key) in this particular build.
Expand Down
63 changes: 63 additions & 0 deletions www/login.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<!DOCTYPE html>
<html>
<head>
<title>CodeChecker</title>

<meta charset="UTF-8">

<link rel="shortcut icon" href="images/favicon.ico" />

<!-- CSS -->

<link type="text/css" rel="stylesheet" href="scripts/plugins/dojo/dijit/themes/claro/claro.css" />
<link type="text/css" rel="stylesheet" href="style/codecheckerviewer.css" />
<link type="text/css" rel="stylesheet" href="style/login.css" />
<link type="text/css" rel="stylesheet" href="style/productlist.css" />

<!-- Third party libraries -->

<script type="text/javascript" src="scripts/plugins/jsplumb/external/jquery-1.9.0-min.js"></script>
<script type="text/javascript" src="scripts/plugins/thrift/thrift.js"></script>

<!-- Services -->

<script type="text/javascript" src="scripts/codechecker-api/shared_types.js"></script>
<script type="text/javascript" src="scripts/codechecker-api/authentication_types.js"></script>
<script type="text/javascript" src="scripts/codechecker-api/codeCheckerAuthentication.js"></script>

<script type="text/javascript" src="scripts/version.js"></script>
<script type="text/javascript" src="scripts/browsersupport.js"></script>

<!-- Configure Dojo -->

<script>
var dojoConfig = {
baseUrl : '',
async : true,
packages : [
{ name : 'dojo', location : 'scripts/plugins/dojo/dojo' },
{ name : 'dijit', location : 'scripts/plugins/dojo/dijit' },
{ name : 'dojox', location : 'scripts/plugins/dojo/dojox' },
{ name : 'codechecker', location : 'scripts/codecheckerviewer' },
{ name : 'login', location : 'scripts/login' }
]
};
</script>

<script type="text/javascript" src="scripts/plugins/dojo/dojo/dojo.js"></script>

<!-- End loading custom scripts -->
</head>
<body class="claro">
<script>
if (!browserCompatible)
setNonCompatibleBrowserMessage();
else
require([
'login/login'],
function (loginPage) {
loginPage();
});
</script>
</body>
</html>
33 changes: 29 additions & 4 deletions www/scripts/codecheckerviewer/HeaderPane.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,18 @@

define([
'dojo/_base/declare',
'dojo/topic',
'dojo/cookie',
'dojo/dom-construct',
'dojo/topic',
'dijit/form/Button',
'dijit/layout/ContentPane',
'dijit/popup',
'dijit/TooltipDialog',
'codechecker/hashHelper',
'codechecker/HeaderMenu',
'codechecker/util'],
function (declare, topic, dom, ContentPane, popup, TooltipDialog, hashHelper,
HeaderMenu, util) {
function (declare, cookie, dom, topic, Button, ContentPane, popup,
TooltipDialog, hashHelper, HeaderMenu, util) {
return declare(ContentPane, {
postCreate : function () {
this.inherited(arguments);
Expand Down Expand Up @@ -62,6 +64,29 @@ function (declare, topic, dom, ContentPane, popup, TooltipDialog, hashHelper,
}, profileMenu);
dom.create('span', { class : 'user-name', innerHTML : user}, header);

var logoutButton = new Button({
class : 'logout-btn',
label : 'Log out',
onClick : function () {
try {
var logoutResult = CC_AUTH_SERVICE.destroySession();

if (logoutResult) {
cookie(CC_AUTH_COOKIE_NAME, 'LOGGED_OUT',
{ path : '/', expires : -1 });

// Redirect the user to the homepage after a successful logout.
window.location.reload(true);
} else {
console.warn("Server rejected logout.");
}
} catch (exc) {
console.error("Logout failed.", exc);
}
}
});
dom.place(logoutButton.domNode, profileMenu);

//--- Permissions ---//

var filter = new CC_AUTH_OBJECTS.PermissionFilter({ given : true });
Expand Down Expand Up @@ -133,7 +158,7 @@ function (declare, topic, dom, ContentPane, popup, TooltipDialog, hashHelper,

var headerMenuButton = new HeaderMenu({
class : 'main-menu-button',
iconClass : 'dijitIconFunction',
iconClass : 'dijitIconFunction'
});
dom.place(headerMenuButton.domNode, this._headerMenu);

Expand Down
Loading

0 comments on commit ae3405d

Please sign in to comment.