diff --git a/BuildingAndDeployment.txt b/BuildingAndDeployment.txt new file mode 100644 index 0000000..28275c9 --- /dev/null +++ b/BuildingAndDeployment.txt @@ -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 + \ No newline at end of file diff --git a/ReadmeThisBranch.txt b/ReadmeThisBranch.txt new file mode 100644 index 0000000..317a275 --- /dev/null +++ b/ReadmeThisBranch.txt @@ -0,0 +1,6 @@ + Updates by maxim@storagecraft.com + +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 diff --git a/WebGitNet.SharedLib/GitUtilities.cs b/WebGitNet.SharedLib/GitUtilities.cs index 299cbbb..ef5555c 100644 --- a/WebGitNet.SharedLib/GitUtilities.cs +++ b/WebGitNet.SharedLib/GitUtilities.cs @@ -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) @@ -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 diff --git a/WebGitNet.SharedLib/SharedControllerBase.cs b/WebGitNet.SharedLib/SharedControllerBase.cs index b9cfc1f..061dd7f 100644 --- a/WebGitNet.SharedLib/SharedControllerBase.cs +++ b/WebGitNet.SharedLib/SharedControllerBase.cs @@ -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() { @@ -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 }); @@ -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"); + } } } diff --git a/WebGitNet.sln b/WebGitNet.sln index 7ed95ba..1ca2b07 100644 --- a/WebGitNet.sln +++ b/WebGitNet.sln @@ -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 diff --git a/WebGitNet/ActionResults/GitStreamResult.cs b/WebGitNet/ActionResults/GitStreamResult.cs index d9d575c..ff577c4 100644 --- a/WebGitNet/ActionResults/GitStreamResult.cs +++ b/WebGitNet/ActionResults/GitStreamResult.cs @@ -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)) { @@ -41,6 +42,7 @@ public GitStreamResult(string commandFormat, string action, string repoPath) } this.repoPath = repoPath; + this.userName = userName; } public override void ExecuteResult(ControllerContext context) @@ -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(() => { diff --git a/WebGitNet/Controllers/ManageController.cs b/WebGitNet/Controllers/ManageController.cs index 1496c4b..ab9dc66 100644 --- a/WebGitNet/Controllers/ManageController.cs +++ b/WebGitNet/Controllers/ManageController.cs @@ -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))) diff --git a/WebGitNet/Controllers/ServiceRpcController.cs b/WebGitNet/Controllers/ServiceRpcController.cs index 97bebbc..0831134 100644 --- a/WebGitNet/Controllers/ServiceRpcController.cs +++ b/WebGitNet/Controllers/ServiceRpcController.cs @@ -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) @@ -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); } } } diff --git a/WebGitNet/Views/Browse/Index.cshtml b/WebGitNet/Views/Browse/Index.cshtml index 3132b33..f7c3f62 100644 --- a/WebGitNet/Views/Browse/Index.cshtml +++ b/WebGitNet/Views/Browse/Index.cshtml @@ -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; +}
@using (Html.BeginForm("Search", "Search", FormMethod.Get, new { @class = "form form-search" })) @@ -28,7 +32,14 @@

- Create a new repo + @if (WeCanCreateRepo) + { + Create a new repo + } + else + { + Cannot create repo + } @if (archived) { View current repos diff --git a/WebGitNet/Views/Manage/Create.cshtml b/WebGitNet/Views/Manage/Create.cshtml index 16fa2b2..cc4a4d1 100644 --- a/WebGitNet/Views/Manage/Create.cshtml +++ b/WebGitNet/Views/Manage/Create.cshtml @@ -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; + } +}

diff --git a/WebGitNet/Views/Shared/_Layout.cshtml b/WebGitNet/Views/Shared/_Layout.cshtml index f619e3e..b92dee4 100644 --- a/WebGitNet/Views/Shared/_Layout.cshtml +++ b/WebGitNet/Views/Shared/_Layout.cshtml @@ -2,6 +2,10 @@ @{ var breadcrumbs = (BreadCrumbTrail)ViewBag.BreadCrumbs; } +@{ + var cntr = (WebGitNet.SharedControllerBase)ViewContext.Controller; + bool WeCanCreateRepo = cntr.AreWeRepoCreator; +} @@ -35,7 +39,14 @@
diff --git a/WebGitNet/Web.config b/WebGitNet/Web.config index f52a99c..2256e88 100644 --- a/WebGitNet/Web.config +++ b/WebGitNet/Web.config @@ -3,6 +3,8 @@ + + diff --git a/WebGitNet/WebGitNet.csproj b/WebGitNet/WebGitNet.csproj index b1d6341..b2cb052 100644 --- a/WebGitNet/WebGitNet.csproj +++ b/WebGitNet/WebGitNet.csproj @@ -41,6 +41,9 @@ prompt 4 ExtendedDesignGuidelineRules.ruleset + true + true + true