Skip to content

Authorization based on HTTP Basic Authentication

Vladimir Kotal edited this page Jul 24, 2019 · 19 revisions

This is an example of how to use the Authorization framework with HTTP Basic authentication.

This example has several presumptions:

  1. you have a clean working OpenGrok instance on your machine (no projects, no groups)
  2. you are able to use Groups subcommand (available when you clone the github repository under tools)
  3. you have the permission to modify Tomcat files
  4. you have set the plugin directory or you know where it is (described above)

Setting up Tomcat

HTTP Basic is supported by Tomcat and we can configure a simple example including users and roles. How we can set up the Tomcat is described in this tutorial using users and roles defined by us.

Tomcat users

We have to modify a Tomcat file to provide information about users and roles in the system. This file is placed in $CATALINA_BASE/conf/tomcat-users.xml. For purpose of this example add these lines to the file inside the element <tomcat-users>.

<role rolename="users"/>
<role rolename="admins"/>
<role rolename="plugins"/>
<role rolename="ghost"/>

<user username="007" password="123456" roles="users"/>
<user username="008" password="123456" roles="plugins"/>
<user username="009" password="123456" roles="users,admins"/>
<user username="00A" password="123456" roles="admins"/>
<user username="00B" password="123456" roles="admins,plugins"/>
<user username="00F" password="123456" roles="ghost"/>

With these lines we tell Tomcat to set up 4 roles and 6 users with their roles assigned. You can modify the usernames and passwords to fit your needs but we will use those in this example.

Application deployment descriptor

Now we have to tell the application that it should use HTTP Basic authentication to protect its sources. We can do this by modifying a web.xml file which is usually placed in the WEB-INF directory in your application. Following lines are necessary to get it work.

<security-constraint>
    <web-resource-collection>                                               
        <web-resource-name>API endpoints are checked separately by the web app</web-resource-name>
        <url-pattern>/api/*</url-pattern>                                   
    </web-resource-collection>                                              
</security-constraint>

<security-constraint>
    <web-resource-collection>
        <web-resource-name>In general everything needs to be authenticated</web-resource-name>
        <url-pattern>/*</url-pattern> <!-- protect the whole application -->
        <url-pattern>/api/v1/search</url-pattern> <!-- protect search endpoint whitelisted above -->
        <url-pattern>/api/v1/suggest/*</url-pattern> <!-- protect suggest endpoint whitelisted above -->
    </web-resource-collection>

    <auth-constraint>
        <role-name>plugins</role-name> <!-- these are the roles from tomcat-users.xml -->
        <role-name>users</role-name> <!-- these are the roles from tomcat-users.xml -->
        <role-name>admins</role-name> <!-- these are the roles from tomcat-users.xml -->
    </auth-constraint>

    <user-data-constraint>
        <!-- transport-guarantee can be CONFIDENTIAL, INTEGRAL, or NONE -->
        <transport-guarantee>NONE</transport-guarantee>
    </user-data-constraint>
</security-constraint>

<security-role>
    <role-name>plugins</role-name>
    <role-name>users</role-name>
    <role-name>admins</role-name>
</security-role>

<login-config>
    <auth-method>BASIC</auth-method>
</login-config>

Watch dog service

We would strongly recommend you to turn on the watchdog service which is suitable for developing plugins, setting the parameter value to true via the "read-only configuration".

This forces the application to reload all plugins in the plugin directory when a modification of any file inside it occurs.

Setting up the repositories

We need to create a couple of test repositories to show the authorization features.

$ cd "$OPENGROK_SRC_ROOT" # navigate to the source folder
$ for name in `seq 1 11`; do mkdir "test-project-$name"; cd "test-project-$name"; echo "Give it a try! Hello from $i" > README.md; git init; git config user.email "x"; git config user.name "y"; git add .; git commit -m "init commit"; cd ..; done;

This should create 10 repositories named "test-project-$number" in the source directory.

Setting up the groupings

See https://github.com/OpenGrok/OpenGrok/wiki/Project-groups

The group names correspond to the roles defined in tomcat-users.xml earlier. The final group structure should look like this now:

$ groups.py -a opengrok.jar -- -l -i readonly_configuration.xml
admins ~ "test-project-1|test-project-2|test-project-3|test-project-4"
users ~ "test-project-5|test-project-6|test-project-7|test-project-8"
    plugins ~ "test-project-9|test-project-10"

Index

Now run the index as usual. Do not forget to use the -R option with group configuration as that will pass the groups into the main configuration. This is the only option how to preserve groups on reindex.

The plugin

Now comes the main part - the plugin itself.

It consists of three parts:

  1. Permission policy
  2. Group discovery
  3. Authorization check

Permission policy

You can use whatever policy you want using even external tools in java or you OS. In this example we will use static map defining which user or group has access to which projects.

private static final Map<String, Set<String>> userProjects = new TreeMap<>();
private static final Map<String, Set<String>> userGroups = new TreeMap<>();

static {
    // all have access to "test-project-11" and some to other "test-project-5" or "test-project-8"
    userProjects.put("007", new TreeSet<>(Arrays.asList(new String[]{"test-project-11", "test-project-5"})));
    userProjects.put("008", new TreeSet<>(Arrays.asList(new String[]{"test-project-11", "test-project-8"})));
    userProjects.put("009", new TreeSet<>(Arrays.asList(new String[]{"test-project-11"})));
    userProjects.put("00A", new TreeSet<>(Arrays.asList(new String[]{"test-project-11"})));
    userProjects.put("00B", new TreeSet<>(Arrays.asList(new String[]{"test-project-11"})));
}

static {
    userGroups.put("007", new TreeSet<>(Arrays.asList(new String[]{})));
    userGroups.put("008", new TreeSet<>(Arrays.asList(new String[]{})));
    userGroups.put("009", new TreeSet<>(Arrays.asList(new String[]{})));
    userGroups.put("00A", new TreeSet<>(Arrays.asList(new String[]{})));
    userGroups.put("00B", new TreeSet<>(Arrays.asList(new String[]{})));
}

Group discovery

The plugin framework which works above our plugin has no idea how specific our plugins wants to be when it does the decisions so there is no way of automate discovery of groups/subgroups/projects in groups. All has to be done in our plugin, preferably only once the plugin is loaded/first used for the particular user (not included in this example).

The very important implication is that allowing a group does not allow its subgroups neither projects. Consider this as a feature since it gives you more freedom for allowing or disallowing particular groups and projects.

This is the most important part - if you have found a group for the user then add all of its projects, repositories, subgroups and their underlying objects.

if ((g = Group.getByName(group)) != null) {
    // group discovery
    for (Project p : g.getRepositories()) {
        userProjects.get(request.getUserPrincipal().getName()).add(p.getDescription());
    }
    for (Project p : g.getProjects()) {
        userProjects.get(request.getUserPrincipal().getName()).add(p.getDescription());
    }
    for (Group grp : g.getDescendants()) {
        for (Project p : grp.getRepositories()) {
            userProjects.get(request.getUserPrincipal().getName()).add(p.getDescription());
        }
        for (Project p : grp.getProjects()) {
            userProjects.get(request.getUserPrincipal().getName()).add(p.getDescription());
        }
        descendants.add(grp.getName());
    }
    while (g != null) {
        descendants.add(g.getName());
        g = g.getParent();
    }

}

Eventually, the userProjects and userGroups are maps of type User-Projects/Groups where we can quickly decide whether the user has the permission for the given entity by just looking into the map and the corresponding set.

Authorization check

The final authorization check is just simple - check if the map contains such Project/Group for the certain user.

// for projects
return userProjects.get(request.getUserPrincipal().getName()).contains(project.getDescription());
// or for groups
return userGroups.get(request.getUserPrincipal().getName()).contains(group.getName());

Running

Now you can compile the plugin as described above in this wiki page and place it into the plugin directory (default $OPENGROK_DATA_ROOT/plugins). If you enabled watchdog service then you are ready to see the results, otherwise you need to restart the application.

When you enter the application, the page immediately fires an login form where you can enter the credentials (as written in tomcat users). Depending on what you have entered you should see filtered results on the main page and even when you search for anything you should not be able to access other projects.

You can try other accounts by logging out by forgetting the session (ctrl+shift+delete + forget current session/this can vary in different browsers).

Complete code

HttpBasicAuthorizationPlugin.java