aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Jason A. Donenfeld <Jason@zx2c4.com>2014-01-15 05:49:31 (JST)
committerGravatar Jason A. Donenfeld <Jason@zx2c4.com>2014-01-16 10:28:12 (JST)
commitd6e9200cc35411f3f27426b608bcfdef9348e6d3 (patch)
tree9cdd921b03465458d10b99ff4357f79a810501c0
parent3741254a6989b2837cd8d20480f152f0096bcb9a (diff)
downloadcgit-d6e9200cc35411f3f27426b608bcfdef9348e6d3.zip
cgit-d6e9200cc35411f3f27426b608bcfdef9348e6d3.tar.gz
auth: add basic authentication filter framework
This leverages the new lua support. See filters/simple-authentication.lua for explaination of how this works. There is also additional documentation in cgitrc.5.txt. Though this is a cookie-based approach, cgit's caching mechanism is preserved for authenticated pages. Very plugable and extendable depending on user needs. The sample script uses an HMAC-SHA1 based cookie to store the currently logged in user, with an expiration date. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
-rw-r--r--cgit.c96
-rw-r--r--cgit.h7
-rw-r--r--cgitrc.5.txt36
-rw-r--r--filter.c11
-rw-r--r--filters/simple-authentication.lua225
-rw-r--r--ui-shared.c28
6 files changed, 387 insertions, 16 deletions
diff --git a/cgit.c b/cgit.c
index f3fe56b..c52ef33 100644
--- a/cgit.c
+++ b/cgit.c
@@ -192,6 +192,8 @@ static void config_cb(const char *name, const char *value)
192 ctx.cfg.commit_filter = cgit_new_filter(value, COMMIT); 192 ctx.cfg.commit_filter = cgit_new_filter(value, COMMIT);
193 else if (!strcmp(name, "email-filter")) 193 else if (!strcmp(name, "email-filter"))
194 ctx.cfg.email_filter = cgit_new_filter(value, EMAIL); 194 ctx.cfg.email_filter = cgit_new_filter(value, EMAIL);
195 else if (!strcmp(name, "auth-filter"))
196 ctx.cfg.auth_filter = cgit_new_filter(value, AUTH);
195 else if (!strcmp(name, "embedded")) 197 else if (!strcmp(name, "embedded"))
196 ctx.cfg.embedded = atoi(value); 198 ctx.cfg.embedded = atoi(value);
197 else if (!strcmp(name, "max-atom-items")) 199 else if (!strcmp(name, "max-atom-items"))
@@ -378,6 +380,10 @@ static void prepare_context(struct cgit_context *ctx)
378 ctx->env.script_name = getenv("SCRIPT_NAME"); 380 ctx->env.script_name = getenv("SCRIPT_NAME");
379 ctx->env.server_name = getenv("SERVER_NAME"); 381 ctx->env.server_name = getenv("SERVER_NAME");
380 ctx->env.server_port = getenv("SERVER_PORT"); 382 ctx->env.server_port = getenv("SERVER_PORT");
383 ctx->env.http_cookie = getenv("HTTP_COOKIE");
384 ctx->env.http_referer = getenv("HTTP_REFERER");
385 ctx->env.content_length = getenv("CONTENT_LENGTH") ? strtoul(getenv("CONTENT_LENGTH"), NULL, 10) : 0;
386 ctx->env.authenticated = 0;
381 ctx->page.mimetype = "text/html"; 387 ctx->page.mimetype = "text/html";
382 ctx->page.charset = PAGE_ENCODING; 388 ctx->page.charset = PAGE_ENCODING;
383 ctx->page.filename = NULL; 389 ctx->page.filename = NULL;
@@ -593,11 +599,92 @@ static int prepare_repo_cmd(struct cgit_context *ctx)
593 return 0; 599 return 0;
594} 600}
595 601
602static inline void open_auth_filter(struct cgit_context *ctx, const char *function)
603{
604 cgit_open_filter(ctx->cfg.auth_filter, function,
605 ctx->env.http_cookie ? ctx->env.http_cookie : "",
606 ctx->env.request_method ? ctx->env.request_method : "",
607 ctx->env.query_string ? ctx->env.query_string : "",
608 ctx->env.http_referer ? ctx->env.http_referer : "",
609 ctx->env.path_info ? ctx->env.path_info : "",
610 ctx->env.http_host ? ctx->env.http_host : "",
611 ctx->env.https ? ctx->env.https : "",
612 ctx->qry.repo ? ctx->qry.repo : "",
613 ctx->qry.page ? ctx->qry.page : "",
614 ctx->qry.url ? ctx->qry.url : "");
615}
616
617#define MAX_AUTHENTICATION_POST_BYTES 4096
618static inline void authenticate_post(struct cgit_context *ctx)
619{
620 if (ctx->env.http_referer && strlen(ctx->env.http_referer) > 0) {
621 html("Status: 302 Redirect\n");
622 html("Cache-Control: no-cache, no-store\n");
623 htmlf("Location: %s\n", ctx->env.http_referer);
624 } else {
625 html("Status: 501 Missing Referer\n");
626 html("Cache-Control: no-cache, no-store\n\n");
627 exit(0);
628 }
629
630 open_auth_filter(ctx, "authenticate-post");
631 char buffer[MAX_AUTHENTICATION_POST_BYTES];
632 int len;
633 len = ctx->env.content_length;
634 if (len > MAX_AUTHENTICATION_POST_BYTES)
635 len = MAX_AUTHENTICATION_POST_BYTES;
636 if (read(STDIN_FILENO, buffer, len) < 0)
637 die_errno("Could not read POST from stdin");
638 if (write(STDOUT_FILENO, buffer, len) < 0)
639 die_errno("Could not write POST to stdout");
640 /* The filter may now spit out a Set-Cookie: ... */
641 cgit_close_filter(ctx->cfg.auth_filter);
642
643 html("\n");
644 exit(0);
645}
646
647static inline void authenticate_cookie(struct cgit_context *ctx)
648{
649 /* If we don't have an auth_filter, consider all cookies valid, and thus return early. */
650 if (!ctx->cfg.auth_filter) {
651 ctx->env.authenticated = 1;
652 return;
653 }
654
655 /* If we're having something POST'd to /login, we're authenticating POST,
656 * instead of the cookie, so call authenticate_post and bail out early.
657 * This pattern here should match /?p=login with POST. */
658 if (ctx->env.request_method && ctx->qry.page && !ctx->repo && \
659 !strcmp(ctx->env.request_method, "POST") && !strcmp(ctx->qry.page, "login")) {
660 authenticate_post(ctx);
661 return;
662 }
663
664 /* If we've made it this far, we're authenticating the cookie for real, so do that. */
665 open_auth_filter(ctx, "authenticate-cookie");
666 ctx->env.authenticated = cgit_close_filter(ctx->cfg.auth_filter);
667}
668
596static void process_request(void *cbdata) 669static void process_request(void *cbdata)
597{ 670{
598 struct cgit_context *ctx = cbdata; 671 struct cgit_context *ctx = cbdata;
599 struct cgit_cmd *cmd; 672 struct cgit_cmd *cmd;
600 673
674 /* If we're not yet authenticated, no matter what page we're on,
675 * display the authentication body from the auth_filter. This should
676 * never be cached. */
677 if (!ctx->env.authenticated) {
678 ctx->page.title = "Authentication Required";
679 cgit_print_http_headers(ctx);
680 cgit_print_docstart(ctx);
681 cgit_print_pageheader(ctx);
682 open_auth_filter(ctx, "body");
683 cgit_close_filter(ctx->cfg.auth_filter);
684 cgit_print_docend();
685 return;
686 }
687
601 cmd = cgit_get_cmd(ctx); 688 cmd = cgit_get_cmd(ctx);
602 if (!cmd) { 689 if (!cmd) {
603 ctx->page.title = "cgit error"; 690 ctx->page.title = "cgit error";
@@ -911,6 +998,7 @@ int main(int argc, const char **argv)
911 int err, ttl; 998 int err, ttl;
912 999
913 cgit_init_filters(); 1000 cgit_init_filters();
1001 atexit(cgit_cleanup_filters);
914 1002
915 prepare_context(&ctx); 1003 prepare_context(&ctx);
916 cgit_repolist.length = 0; 1004 cgit_repolist.length = 0;
@@ -948,18 +1036,22 @@ int main(int argc, const char **argv)
948 cgit_parse_url(ctx.qry.url); 1036 cgit_parse_url(ctx.qry.url);
949 } 1037 }
950 1038
1039 /* Before we go any further, we set ctx.env.authenticated by checking to see
1040 * if the supplied cookie is valid. All cookies are valid if there is no
1041 * auth_filter. If there is an auth_filter, the filter decides. */
1042 authenticate_cookie(&ctx);
1043
951 ttl = calc_ttl(); 1044 ttl = calc_ttl();
952 if (ttl < 0) 1045 if (ttl < 0)
953 ctx.page.expires += 10 * 365 * 24 * 60 * 60; /* 10 years */ 1046 ctx.page.expires += 10 * 365 * 24 * 60 * 60; /* 10 years */
954 else 1047 else
955 ctx.page.expires += ttl * 60; 1048 ctx.page.expires += ttl * 60;
956 if (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")) 1049 if (!ctx.env.authenticated || (ctx.env.request_method && !strcmp(ctx.env.request_method, "HEAD")))
957 ctx.cfg.nocache = 1; 1050 ctx.cfg.nocache = 1;
958 if (ctx.cfg.nocache) 1051 if (ctx.cfg.nocache)
959 ctx.cfg.cache_size = 0; 1052 ctx.cfg.cache_size = 0;
960 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root, 1053 err = cache_process(ctx.cfg.cache_size, ctx.cfg.cache_root,
961 ctx.qry.raw, ttl, process_request, &ctx); 1054 ctx.qry.raw, ttl, process_request, &ctx);
962 cgit_cleanup_filters();
963 if (err) 1055 if (err)
964 cgit_print_error("Error processing page: %s (%d)", 1056 cgit_print_error("Error processing page: %s (%d)",
965 strerror(err), err); 1057 strerror(err), err);
diff --git a/cgit.h b/cgit.h
index e200a06..496d0f6 100644
--- a/cgit.h
+++ b/cgit.h
@@ -53,7 +53,7 @@ typedef void (*filepair_fn)(struct diff_filepair *pair);
53typedef void (*linediff_fn)(char *line, int len); 53typedef void (*linediff_fn)(char *line, int len);
54 54
55typedef enum { 55typedef enum {
56 ABOUT, COMMIT, SOURCE, EMAIL 56 ABOUT, COMMIT, SOURCE, EMAIL, AUTH
57} filter_type; 57} filter_type;
58 58
59struct cgit_filter { 59struct cgit_filter {
@@ -252,6 +252,7 @@ struct cgit_config {
252 struct cgit_filter *commit_filter; 252 struct cgit_filter *commit_filter;
253 struct cgit_filter *source_filter; 253 struct cgit_filter *source_filter;
254 struct cgit_filter *email_filter; 254 struct cgit_filter *email_filter;
255 struct cgit_filter *auth_filter;
255}; 256};
256 257
257struct cgit_page { 258struct cgit_page {
@@ -278,6 +279,10 @@ struct cgit_environment {
278 const char *script_name; 279 const char *script_name;
279 const char *server_name; 280 const char *server_name;
280 const char *server_port; 281 const char *server_port;
282 const char *http_cookie;
283 const char *http_referer;
284 unsigned int content_length;
285 int authenticated;
281}; 286};
282 287
283struct cgit_context { 288struct cgit_context {
diff --git a/cgitrc.5.txt b/cgitrc.5.txt
index 170e825..c45dbd3 100644
--- a/cgitrc.5.txt
+++ b/cgitrc.5.txt
@@ -42,6 +42,13 @@ agefile::
42 hh:mm:ss". You may want to generate this file from a post-receive 42 hh:mm:ss". You may want to generate this file from a post-receive
43 hook. Default value: "info/web/last-modified". 43 hook. Default value: "info/web/last-modified".
44 44
45auth-filter::
46 Specifies a command that will be invoked for authenticating repository
47 access. Receives quite a few arguments, and data on both stdin and
48 stdout for authentication processing. Details follow later in this
49 document. If no auth-filter is specified, no authentication is
50 performed. Default value: none. See also: "FILTER API".
51
45branch-sort:: 52branch-sort::
46 Flag which, when set to "age", enables date ordering in the branch ref 53 Flag which, when set to "age", enables date ordering in the branch ref
47 list, and when set to "name" enables ordering by branch name. Default 54 list, and when set to "name" enables ordering by branch name. Default
@@ -605,6 +612,8 @@ specification with the relevant string; available values are:
605 URL escapes for a path and writes 'str' to the webpage. 612 URL escapes for a path and writes 'str' to the webpage.
606 'html_url_arg(str)':: 613 'html_url_arg(str)'::
607 URL escapes for an argument and writes 'str' to the webpage. 614 URL escapes for an argument and writes 'str' to the webpage.
615 'html_include(file)'::
616 Includes 'file' in webpage.
608 617
609 618
610Parameters are provided to filters as follows. 619Parameters are provided to filters as follows.
@@ -635,7 +644,32 @@ source filter::
635 file that is to be filtered is available on standard input and the 644 file that is to be filtered is available on standard input and the
636 filtered contents is expected on standard output. 645 filtered contents is expected on standard output.
637 646
638Also, all filters are handed the following environment variables: 647auth filter::
648 The authentication filter receives 11 parameters:
649 - filter action, explained below, which specifies which action the
650 filter is called for
651 - http cookie
652 - http method
653 - http referer
654 - http path
655 - http https flag
656 - cgit repo
657 - cgit page
658 - cgit url
659 When the filter action is "body", this filter must write to output the
660 HTML for displaying the login form, which POSTs to "/?p=login". When
661 the filter action is "authenticate-cookie", this filter must validate
662 the http cookie and return a 0 if it is invalid or 1 if it is invalid,
663 in the exit code / close function. If the filter action is
664 "authenticate-post", this filter receives POST'd parameters on
665 standard input, and should write to output one or more "Set-Cookie"
666 HTTP headers, each followed by a newline.
667
668 Please see `filters/simple-authentication.lua` for a clear example
669 script that may be modified.
670
671
672All filters are handed the following environment variables:
639 673
640- CGIT_REPO_URL (from repo.url) 674- CGIT_REPO_URL (from repo.url)
641- CGIT_REPO_NAME (from repo.name) 675- CGIT_REPO_NAME (from repo.name)
diff --git a/filter.c b/filter.c
index 0cce7bb..a5e5e4b 100644
--- a/filter.c
+++ b/filter.c
@@ -244,6 +244,11 @@ static int html_url_arg_lua_filter(lua_State *lua_state)
244 return hook_lua_filter(lua_state, html_url_arg); 244 return hook_lua_filter(lua_state, html_url_arg);
245} 245}
246 246
247static int html_include_lua_filter(lua_State *lua_state)
248{
249 return hook_lua_filter(lua_state, (void (*)(const char *))html_include);
250}
251
247static void cleanup_lua_filter(struct cgit_filter *base) 252static void cleanup_lua_filter(struct cgit_filter *base)
248{ 253{
249 struct lua_filter *filter = (struct lua_filter *)base; 254 struct lua_filter *filter = (struct lua_filter *)base;
@@ -279,6 +284,8 @@ static int init_lua_filter(struct lua_filter *filter)
279 lua_setglobal(filter->lua_state, "html_url_path"); 284 lua_setglobal(filter->lua_state, "html_url_path");
280 lua_pushcfunction(filter->lua_state, html_url_arg_lua_filter); 285 lua_pushcfunction(filter->lua_state, html_url_arg_lua_filter);
281 lua_setglobal(filter->lua_state, "html_url_arg"); 286 lua_setglobal(filter->lua_state, "html_url_arg");
287 lua_pushcfunction(filter->lua_state, html_include_lua_filter);
288 lua_setglobal(filter->lua_state, "html_include");
282 289
283 if (luaL_dofile(filter->lua_state, filter->script_file)) { 290 if (luaL_dofile(filter->lua_state, filter->script_file)) {
284 error_lua_filter(filter); 291 error_lua_filter(filter);
@@ -409,6 +416,10 @@ struct cgit_filter *cgit_new_filter(const char *cmd, filter_type filtertype)
409 colon = NULL; 416 colon = NULL;
410 417
411 switch (filtertype) { 418 switch (filtertype) {
419 case AUTH:
420 argument_count = 11;
421 break;
422
412 case EMAIL: 423 case EMAIL:
413 argument_count = 2; 424 argument_count = 2;
414 break; 425 break;
diff --git a/filters/simple-authentication.lua b/filters/simple-authentication.lua
new file mode 100644
index 0000000..4cd4983
--- /dev/null
+++ b/filters/simple-authentication.lua
@@ -0,0 +1,225 @@
1-- This script may be used with the auth-filter. Be sure to configure it as you wish.
2--
3-- Requirements:
4-- luacrypto >= 0.3
5-- <http://mkottman.github.io/luacrypto/>
6--
7
8
9--
10--
11-- Configure these variables for your settings.
12--
13--
14
15local protected_repos = {
16 glouglou = { laurent = true, jason = true },
17 qt = { jason = true, bob = true }
18}
19
20local users = {
21 jason = "secretpassword",
22 laurent = "s3cr3t",
23 bob = "ilikelua"
24}
25
26local secret = "BE SURE TO CUSTOMIZE THIS STRING TO SOMETHING BIG AND RANDOM"
27
28
29
30--
31--
32-- Authentication functions follow below. Swap these out if you want different authentication semantics.
33--
34--
35
36-- Sets HTTP cookie headers based on post
37function authenticate_post()
38 local password = users[post["username"]]
39 -- TODO: Implement time invariant string comparison function to mitigate against timing attack.
40 if password == nil or password ~= post["password"] then
41 construct_cookie("", "cgitauth")
42 else
43 construct_cookie(post["username"], "cgitauth")
44 end
45 return 0
46end
47
48
49-- Returns 1 if the cookie is valid and 0 if it is not.
50function authenticate_cookie()
51 accepted_users = protected_repos[cgit["repo"]]
52 if accepted_users == nil then
53 -- We return as valid if the repo is not protected.
54 return 1
55 end
56
57 local username = validate_cookie(get_cookie(http["cookie"], "cgitauth"))
58 if username == nil or not accepted_users[username] then
59 return 0
60 else
61 return 1
62 end
63end
64
65-- Prints the html for the login form.
66function body()
67 html("<h2>Authentication Required</h2>")
68 html("<form method='post' action='")
69 html_attr(cgit["login"])
70 html("'>")
71 html("<table>")
72 html("<tr><td><label for='username'>Username:</label></td><td><input id='username' name='username' autofocus /></td></tr>")
73 html("<tr><td><label for='password'>Password:</label></td><td><input id='password' name='password' type='password' /></td></tr>")
74 html("<tr><td colspan='2'><input value='Login' type='submit' /></td></tr>")
75 html("</table></form>")
76
77 return 0
78end
79
80
81--
82--
83-- Cookie construction and validation helpers.
84--
85--
86
87local crypto = require("crypto")
88
89-- Returns username of cookie if cookie is valid. Otherwise returns nil.
90function validate_cookie(cookie)
91 local i = 0
92 local username = ""
93 local expiration = 0
94 local salt = ""
95 local hmac = ""
96
97 if cookie:len() < 3 or cookie:sub(1, 1) == "|" then
98 return nil
99 end
100
101 for component in string.gmatch(cookie, "[^|]+") do
102 if i == 0 then
103 username = component
104 elseif i == 1 then
105 expiration = tonumber(component)
106 if expiration == nil then
107 expiration = 0
108 end
109 elseif i == 2 then
110 salt = component
111 elseif i == 3 then
112 hmac = component
113 else
114 break
115 end
116 i = i + 1
117 end
118
119 if hmac == nil or hmac:len() == 0 then
120 return nil
121 end
122
123 -- TODO: implement time invariant comparison to prevent against timing attack.
124 if hmac ~= crypto.hmac.digest("sha1", username .. "|" .. tostring(expiration) .. "|" .. salt, secret) then
125 return nil
126 end
127
128 if expiration <= os.time() then
129 return nil
130 end
131
132 return username:lower()
133end
134
135function construct_cookie(username, cookie)
136 local authstr = ""
137 if username:len() > 0 then
138 -- One week expiration time
139 local expiration = os.time() + 604800
140 local salt = crypto.hex(crypto.rand.bytes(16))
141
142 authstr = username .. "|" .. tostring(expiration) .. "|" .. salt
143 authstr = authstr .. "|" .. crypto.hmac.digest("sha1", authstr, secret)
144 end
145
146 html("Set-Cookie: " .. cookie .. "=" .. authstr .. "; HttpOnly")
147 if http["https"] == "yes" or http["https"] == "on" or http["https"] == "1" then
148 html("; secure")
149 end
150 html("\n")
151end
152
153--
154--
155-- Wrapper around filter API follows below, exposing the http table, the cgit table, and the post table to the above functions.
156--
157--
158
159local actions = {}
160actions["authenticate-post"] = authenticate_post
161actions["authenticate-cookie"] = authenticate_cookie
162actions["body"] = body
163
164function filter_open(...)
165 action = actions[select(1, ...)]
166
167 http = {}
168 http["cookie"] = select(2, ...)
169 http["method"] = select(3, ...)
170 http["query"] = select(4, ...)
171 http["referer"] = select(5, ...)
172 http["path"] = select(6, ...)
173 http["host"] = select(7, ...)
174 http["https"] = select(8, ...)
175
176 cgit = {}
177 cgit["repo"] = select(9, ...)
178 cgit["page"] = select(10, ...)
179 cgit["url"] = select(11, ...)
180
181 cgit["login"] = ""
182 for _ in cgit["url"]:gfind("/") do
183 cgit["login"] = cgit["login"] .. "../"
184 end
185 cgit["login"] = cgit["login"] .. "?p=login"
186
187end
188
189function filter_close()
190 return action()
191end
192
193function filter_write(str)
194 post = parse_qs(str)
195end
196
197
198--
199--
200-- Utility functions follow below, based on keplerproject/wsapi.
201--
202--
203
204function url_decode(str)
205 if not str then
206 return ""
207 end
208 str = string.gsub(str, "+", " ")
209 str = string.gsub(str, "%%(%x%x)", function(h) return string.char(tonumber(h, 16)) end)
210 str = string.gsub(str, "\r\n", "\n")
211 return str
212end
213
214function parse_qs(qs)
215 local tab = {}
216 for key, val in string.gmatch(qs, "([^&=]+)=([^&=]*)&?") do
217 tab[url_decode(key)] = url_decode(val)
218 end
219 return tab
220end
221
222function get_cookie(cookies, name)
223 cookies = string.gsub(";" .. cookies .. ";", "%s*;%s*", ";")
224 return url_decode(string.match(cookies, ";" .. name .. "=(.-);"))
225end
diff --git a/ui-shared.c b/ui-shared.c
index abe15cd..4f47c50 100644
--- a/ui-shared.c
+++ b/ui-shared.c
@@ -641,6 +641,8 @@ void cgit_print_http_headers(struct cgit_context *ctx)
641 if (ctx->page.filename) 641 if (ctx->page.filename)
642 htmlf("Content-Disposition: inline; filename=\"%s\"\n", 642 htmlf("Content-Disposition: inline; filename=\"%s\"\n",
643 ctx->page.filename); 643 ctx->page.filename);
644 if (!ctx->env.authenticated)
645 html("Cache-Control: no-cache, no-store\n");
644 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified)); 646 htmlf("Last-Modified: %s\n", http_date(ctx->page.modified));
645 htmlf("Expires: %s\n", http_date(ctx->page.expires)); 647 htmlf("Expires: %s\n", http_date(ctx->page.expires));
646 if (ctx->page.etag) 648 if (ctx->page.etag)
@@ -814,14 +816,16 @@ static void print_header(struct cgit_context *ctx)
814 cgit_index_link("index", NULL, NULL, NULL, NULL, 0); 816 cgit_index_link("index", NULL, NULL, NULL, NULL, 0);
815 html(" : "); 817 html(" : ");
816 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL); 818 cgit_summary_link(ctx->repo->name, ctx->repo->name, NULL, NULL);
817 html("</td><td class='form'>"); 819 if (ctx->env.authenticated) {
818 html("<form method='get' action=''>\n"); 820 html("</td><td class='form'>");
819 cgit_add_hidden_formfields(0, 1, ctx->qry.page); 821 html("<form method='get' action=''>\n");
820 html("<select name='h' onchange='this.form.submit();'>\n"); 822 cgit_add_hidden_formfields(0, 1, ctx->qry.page);
821 for_each_branch_ref(print_branch_option, ctx->qry.head); 823 html("<select name='h' onchange='this.form.submit();'>\n");
822 html("</select> "); 824 for_each_branch_ref(print_branch_option, ctx->qry.head);
823 html("<input type='submit' name='' value='switch'/>"); 825 html("</select> ");
824 html("</form>"); 826 html("<input type='submit' name='' value='switch'/>");
827 html("</form>");
828 }
825 } else 829 } else
826 html_txt(ctx->cfg.root_title); 830 html_txt(ctx->cfg.root_title);
827 html("</td></tr>\n"); 831 html("</td></tr>\n");
@@ -843,11 +847,11 @@ static void print_header(struct cgit_context *ctx)
843void cgit_print_pageheader(struct cgit_context *ctx) 847void cgit_print_pageheader(struct cgit_context *ctx)
844{ 848{
845 html("<div id='cgit'>"); 849 html("<div id='cgit'>");
846 if (!ctx->cfg.noheader) 850 if (!ctx->env.authenticated || !ctx->cfg.noheader)
847 print_header(ctx); 851 print_header(ctx);
848 852
849 html("<table class='tabs'><tr><td>\n"); 853 html("<table class='tabs'><tr><td>\n");
850 if (ctx->repo) { 854 if (ctx->env.authenticated && ctx->repo) {
851 cgit_summary_link("summary", NULL, hc(ctx, "summary"), 855 cgit_summary_link("summary", NULL, hc(ctx, "summary"),
852 ctx->qry.head); 856 ctx->qry.head);
853 cgit_refs_link("refs", NULL, hc(ctx, "refs"), ctx->qry.head, 857 cgit_refs_link("refs", NULL, hc(ctx, "refs"), ctx->qry.head,
@@ -886,7 +890,7 @@ void cgit_print_pageheader(struct cgit_context *ctx)
886 html("'/>\n"); 890 html("'/>\n");
887 html("<input type='submit' value='search'/>\n"); 891 html("<input type='submit' value='search'/>\n");
888 html("</form>\n"); 892 html("</form>\n");
889 } else { 893 } else if (ctx->env.authenticated) {
890 site_link(NULL, "index", NULL, hc(ctx, "repolist"), NULL, NULL, 0); 894 site_link(NULL, "index", NULL, hc(ctx, "repolist"), NULL, NULL, 0);
891 if (ctx->cfg.root_readme) 895 if (ctx->cfg.root_readme)
892 site_link("about", "about", NULL, hc(ctx, "about"), 896 site_link("about", "about", NULL, hc(ctx, "about"),
@@ -902,7 +906,7 @@ void cgit_print_pageheader(struct cgit_context *ctx)
902 html("</form>"); 906 html("</form>");
903 } 907 }
904 html("</td></tr></table>\n"); 908 html("</td></tr></table>\n");
905 if (ctx->qry.vpath) { 909 if (ctx->env.authenticated && ctx->qry.vpath) {
906 html("<div class='path'>"); 910 html("<div class='path'>");
907 html("path: "); 911 html("path: ");
908 cgit_print_path_crumbs(ctx, ctx->qry.vpath); 912 cgit_print_path_crumbs(ctx, ctx->qry.vpath);