Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions BuildingAndDeployment.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
Build machine required configuration:
some Git client (to connect to github.com)
Visual Studio 2010 (this was tried with SP1 on Ultimate SKU, can possibly work on other
SKUs and without SP1)
ASP.NET MVC 4 (install as separate download from MS)
yes, a must for Studio to open the project

Building:
get WebGitNet from github.com, use the branch you want (like MoreUserAccessControl)
open the .sln file by VS2010
the Studio can fail to install something related to IIS Express, but the project will
open and build OK anyway
the following should be done only once
in VS2010 Publish toolbar choose "New" from combobox, then "File System" and a target dir
this will create a profile named like "Profile1"
in VS2010 "Build Solution"
in VS2010 go to Solution explorer, to WebGitNet subproject,
right-click for a subproject properties context menu
Build Deployment Package
Publish
to the aforementioned profile (like "Profile1")
here you have a copy of the web app's file/directory structure in the publish target dir
NOTE for ASP.NET novices: the source .cs files of controllers/models are NOT deployed to the
web server, only the .cshtml files for views. The latter ones can be edited in-place
on the web server and the updates will come into effect ASAP. The former ones must be
updated on the build machine and the web app should be rebuilt and re-deployed.

Web server machine required configuration:
IIS (tried on IIS8/Server 2012, can also probably work on older IIS versions)
proper certificate setup if you want to use SSL
Basic auth (install as Windows component) is a must, since:
Digest auth requires a domain
Windows (aka NTLM) auth has issues with Linux clients like CentOS
(and maybe RHEL/Fedora since they are similar)
which have obsolete "curl" package with broken Windows auth client feature
(actually what is broken there is Negotiate and its Kerberos attempt,
Windows auth itself is fine, but Git always invokes "curl" with Negotiate option)
Windows auth is OK for Windows-only client environments though (or if you will update
"curl" to newer version on CentOS and similar Linuxen)
proper security/access rights limitations setup
you should not disable POST, this also disables pull, fetch and clone, not only push
NOTE: the security-related changes in Windows user group membership do NOT immediately
come to effect in IIS web apps, requiring IIS restart each time
to fix this, use:
HKLM\System\CurrentControlSet\Services\InetInfo\Parameters
UserTokenTTL DWORD in seconds
set to 1 second
restart IIS after this
no need in WebDAV - the web app implements the Git's "smart HTTP" protocol, which is
enough for us, the web server will not implement the Git's "dumb HTTP" protocol which is
based on WebDAV
ASP.NET 4.5 (install as Windows component), can also be tried with earlier versions
ASP.NET MVC 4 (install as separate download from MS)
typical security setup of the web server
add GitUsers Windows group, which is the only one which can access the web app
add GitWebApp user and an App Pool running with this user
Git repo directories must have Full Control for both GitWebApp and GitUsers
also add GitRepoCreators and GitReaders groups (see below)
mSysGit
is a must, since the web app only wraps it around and does not contain the full Git
implementation

Web server machine:
copy the whole subtree (under the target dir of a build/publish process
on the build machine) to a new web app's directory on the web server machine
create a Web App in IIS mgmt, running as GitWebApp user
set the web.config parameters (below)

web.config parameters
RepositoriesPath
path to a directory which contains all Git repos managed by this web app's instance
each subdir of this dir is considered to be a Git repo
newly created repos are also subdirs here
see the above security setup notes for ACLs on this dir
GitCommand
full pathname of the installed git.exe from mSysGit
the default value of "C:\Program Files (x86)\Git\bin\git.exe" is OK for most needs
groups
ReadOnlyLimitedGroupName
name of Windows user group
the members of this group are prohibited from pushing to repositories, but
can still be allowed (by IIS's security settings) to view/browse the repositories
via the web app and to fetch/pull/clone them via Git
RepoCreatorsGroupName
name of Windows user group
the user must be a member of this group to be able to create repos

6 changes: 6 additions & 0 deletions ReadmeThisBranch.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Updates by [email protected]

1) git-receive-pack handler now sets USER environment variable to user logged on to client's browser
(for Git hooks to work, authorization-related and maybe others)
2) now shows the logged-on user name everywhere
3) now fails git-receive-pack if the logged-on user is in the "limited readers" group
13 changes: 13 additions & 0 deletions WebGitNet.SharedLib/GitUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,16 @@ public static string Execute(string command, string workingDir, Encoding outputE
}

public static Process Start(string command, string workingDir, bool redirectInput = false, bool redirectError = false, Encoding outputEncoding = null)
{
return StartInner(command, workingDir, redirectInput, redirectError, outputEncoding, null);
}

public static Process StartWithUserName(string command, string workingDir, string userName, bool redirectInput = false, bool redirectError = false, Encoding outputEncoding = null)
{
return StartInner(command, workingDir, redirectInput, redirectError, outputEncoding, userName);
}

private static Process StartInner(string command, string workingDir, bool redirectInput, bool redirectError, Encoding outputEncoding, string userName)
{
var git = WebConfigurationManager.AppSettings["GitCommand"];
var startInfo = new ProcessStartInfo(git, command)
Expand All @@ -101,6 +111,9 @@ public static Process Start(string command, string workingDir, bool redirectInpu
CreateNoWindow = true,
};

if (userName != null)
startInfo.EnvironmentVariables["USER"] = userName;

Process process = null, returnProcess = null;
IDisposable trace = null, traceClosure = null;
try
Expand Down
43 changes: 43 additions & 0 deletions WebGitNet.SharedLib/SharedControllerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ public abstract partial class SharedControllerBase : Controller
{
private readonly FileManager fileManager;
private readonly BreadCrumbTrail breadCrumbs;
private bool areWeLimitedReader;
private bool areWeRepoCreator;

public SharedControllerBase()
{
Expand All @@ -25,6 +27,14 @@ public SharedControllerBase()
ViewBag.BreadCrumbs = this.breadCrumbs;
}

protected override void Initialize(System.Web.Routing.RequestContext requestContext)
{
base.Initialize(requestContext);
// Set our user access rights flags
areWeLimitedReader = areWeInReadOnlyLimitedGroup();
areWeRepoCreator = areWeInRepoCreatorGroup();
}

protected void AddRepoBreadCrumb(string repo)
{
this.BreadCrumbs.Append("Browse", "ViewRepo", repo, new { repo });
Expand All @@ -45,5 +55,38 @@ public BreadCrumbTrail BreadCrumbs
return this.breadCrumbs;
}
}

protected bool AreWeLimitedReader
{
get
{
return this.areWeLimitedReader;
}
}

public bool AreWeRepoCreator
{
get
{
return this.areWeRepoCreator;
}
}

private bool areWeInConfigGroup(string groupKeyName)
{
var clientPrincipal = (System.Security.Principal.WindowsPrincipal)User;
var groupName = WebConfigurationManager.AppSettings[groupKeyName];
return clientPrincipal.IsInRole(groupName);
}

private bool areWeInReadOnlyLimitedGroup()
{
return areWeInConfigGroup("ReadOnlyLimitedGroupName");
}

private bool areWeInRepoCreatorGroup()
{
return areWeInConfigGroup("RepoCreatorsGroupName");
}
}
}
4 changes: 2 additions & 2 deletions WebGitNet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{C3153971-94BC-4991-8790-41B046680108}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C3153971-94BC-4991-8790-41B046680108}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C3153971-94BC-4991-8790-41B046680108}.Debug|Any CPU.ActiveCfg = Release|Any CPU
{C3153971-94BC-4991-8790-41B046680108}.Debug|Any CPU.Build.0 = Release|Any CPU
{C3153971-94BC-4991-8790-41B046680108}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C3153971-94BC-4991-8790-41B046680108}.Release|Any CPU.Build.0 = Release|Any CPU
{F00879C2-B2F3-4202-B1BF-12A875AF4F7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
Expand Down
6 changes: 4 additions & 2 deletions WebGitNet/ActionResults/GitStreamResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ public class GitStreamResult : ActionResult
private readonly string commandFormat;
private readonly string action;
private readonly string repoPath;
private readonly string userName;

public GitStreamResult(string commandFormat, string action, string repoPath)
public GitStreamResult(string commandFormat, string action, string repoPath, string userName = null)
{
if (string.IsNullOrEmpty(commandFormat))
{
Expand All @@ -41,6 +42,7 @@ public GitStreamResult(string commandFormat, string action, string repoPath)
}

this.repoPath = repoPath;
this.userName = userName;
}

public override void ExecuteResult(ControllerContext context)
Expand All @@ -52,7 +54,7 @@ public override void ExecuteResult(ControllerContext context)
response.ContentType = "application/x-git-" + this.action + "-result";
response.BufferOutput = false;

using (var git = GitUtilities.Start(string.Format(this.commandFormat, this.action), this.repoPath, redirectInput: true))
using (var git = GitUtilities.StartWithUserName(string.Format(this.commandFormat, this.action), this.repoPath, this.userName, redirectInput: true))
{
var readThread = new Thread(() =>
{
Expand Down
2 changes: 2 additions & 0 deletions WebGitNet/Controllers/ManageController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ public ActionResult Create(CreateRepoRequest request)

if (ModelState.IsValid)
{
if( !AreWeRepoCreator )
return new HttpStatusCodeResult(403, "You do not have permission to create repositories");
var invalid = Path.GetInvalidFileNameChars();

if (request.RepoName.Any(c => invalid.Contains(c)))
Expand Down
9 changes: 6 additions & 3 deletions WebGitNet/Controllers/ServiceRpcController.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,13 @@ public ActionResult UploadPack(string url)
[HttpPost]
public ActionResult ReceivePack(string url)
{
return this.ServiceRpc(url, "receive-pack");
if (AreWeLimitedReader)
return new HttpStatusCodeResult(403, "You do not have permission to push to this repo");
string userName = User.Identity.Name;
return this.ServiceRpc(url, "receive-pack", userName);
}

private ActionResult ServiceRpc(string url, string action)
private ActionResult ServiceRpc(string url, string action, string userName = null)
{
var resourceInfo = this.FileManager.GetResourceInfo(url);
if (resourceInfo.FileSystemInfo == null)
Expand All @@ -35,7 +38,7 @@ private ActionResult ServiceRpc(string url, string action)

var repoPath = ((FileInfo)resourceInfo.FileSystemInfo).Directory.FullName;

return new GitStreamResult("{0} --stateless-rpc .", action, repoPath);
return new GitStreamResult("{0} --stateless-rpc .", action, repoPath, userName);
}
}
}
13 changes: 12 additions & 1 deletion WebGitNet/Views/Browse/Index.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
var archived = (bool)ViewBag.Archived;
ViewBag.Title = archived ? "Archived Repositories" : "Repositories";
}
@{
var cntr = (WebGitNet.SharedControllerBase)ViewContext.Controller;
bool WeCanCreateRepo = cntr.AreWeRepoCreator;
}

<div class="pull-right">
@using (Html.BeginForm("Search", "Search", FormMethod.Get, new { @class = "form form-search" }))
Expand All @@ -28,7 +32,14 @@
</table>

<p>
<a class="btn btn-link" href="@Url.Action("Create", "Manage")"><i class="icon-plus"></i> Create a new repo</a>
@if (WeCanCreateRepo)
{
<a class="btn btn-link" href="@Url.Action("Create", "Manage")"><i class="icon-plus"></i> Create a new repo</a>
}
else
{
<i>Cannot create repo</i>
}
@if (archived)
{
<a class="btn btn-link" href="@Url.Action("Index", "Browse")"><i class="icon-folder-open"></i> View current repos</a>
Expand Down
12 changes: 12 additions & 0 deletions WebGitNet/Views/Manage/Create.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,18 @@
@{
ViewBag.Title = "Create Repo";
}
@{
var ctx = Context.ApplicationInstance.Context;
var cntr = (WebGitNet.Controllers.ManageController)ViewContext.Controller;
if( !cntr.AreWeRepoCreator )
{
var httpRsp = ctx.Response;
httpRsp.StatusCode = 403;
httpRsp.StatusDescription = "You are not allowed to create repositories";
httpRsp.SubStatusCode = 3;
return;
}
}

<form action="" class="form-horizontal" method="post">
<fieldset>
Expand Down
16 changes: 15 additions & 1 deletion WebGitNet/Views/Shared/_Layout.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@
@{
var breadcrumbs = (BreadCrumbTrail)ViewBag.BreadCrumbs;
}
@{
var cntr = (WebGitNet.SharedControllerBase)ViewContext.Controller;
bool WeCanCreateRepo = cntr.AreWeRepoCreator;
}
<!DOCTYPE html>
<html>
<head>
Expand Down Expand Up @@ -35,7 +39,14 @@
<div class="nav-collapse">
<ul class="nav pull-right">
<li>@Html.ActionLink("Browse", "Index", "Browse")</li>
<li>@Html.ActionLink("Create Repo", "Create", "Manage")</li>
@if (WeCanCreateRepo)
{
<li>@Html.ActionLink("Create Repo", "Create", "Manage")</li>
}
else
{
<li>@Html.Label("Cannot Create Repo")</li>
}
<li class="dropdown">
<a class="dropdown-toggle" data-toggle="dropdown" href="#"><i class="icon-feed icon-white"></i> Feeds <span class="caret"></span></a>
<ul class="dropdown-menu">
Expand Down Expand Up @@ -69,6 +80,9 @@
}
</ul>
}
<ul class="pull-right">
You're logged on as @Context.ApplicationInstance.Context.User.Identity.Name
</ul>
</div>
</div>
<div class="container">
Expand Down
2 changes: 2 additions & 0 deletions WebGitNet/Web.config
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<appSettings>
<add key="RepositoriesPath" value="C:\Repos\Git" />
<add key="GitCommand" value="C:\Program Files (x86)\Git\bin\git.exe" />
<add key="ReadOnlyLimitedGroupName" value="GitReaders" />
<add key="RepoCreatorsGroupName" value="GitRepoCreators" />
<add key="GravatarFallBack" value="wavatar" />
<add key="Markdown.AutoHyperlink" value="true" />
<add key="Markdown.AutoNewlines" value="true" />
Expand Down
3 changes: 3 additions & 0 deletions WebGitNet/WebGitNet.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodeAnalysisRuleSet>ExtendedDesignGuidelineRules.ruleset</CodeAnalysisRuleSet>
<ExcludeGeneratedDebugSymbol>true</ExcludeGeneratedDebugSymbol>
<ExcludeApp_Data>true</ExcludeApp_Data>
<PackageAsSingleFile>true</PackageAsSingleFile>
</PropertyGroup>
<ItemGroup>
<Reference Include="Castle.Core, Version=3.0.0.0, Culture=neutral, PublicKeyToken=407dd0808d44fbdc, processorArchitecture=MSIL">
Expand Down