ASP. NET Core open-source GitServer implements its own GitHub, gitservergithub
ASP. NET Core 2.0 open source Git HTTP Server, implementation is similar to GitHub, GitLab.
GitHub: https://github.com/linezero/GitServer
Set
"GitSettings": { "BasePath": "D:\\Git", "GitPath": "git" }
Install Git first and make sure that the git command can be executed. GitPath can be the absolute path of git.
Current functions
- Create a repository
- Browse warehouse
- Git client push pull
- The Database supports SQLite, MSSQL, and MySQL.
- Allows users to manage warehouses.
For more functions, see readme. You are also welcome to contribute your support.
Git Interaction
LibGit2Sharp is used to operate the Git library to create and read the Repository Information and delete the repository.
The main code is as follows:
public Repository CreateRepository(string name) { string path = Path.Combine(Settings.BasePath, name); Repository repo = new Repository(Repository.Init(path, true)); return repo; } public Repository CreateRepository(string name, string remoteUrl) { var path = Path.Combine(Settings.BasePath, name); try { using (var repo = new Repository(Repository.Init(path, true))) { repo.Config.Set("core.logallrefupdates", true); repo.Network.Remotes.Add("origin", remoteUrl, "+refs/*:refs/*"); var logMessage = ""; foreach (var remote in repo.Network.Remotes) { IEnumerable<string> refSpecs = remote.FetchRefSpecs.Select(x => x.Specification); Commands.Fetch(repo, remote.Name, refSpecs, null, logMessage); } return repo; } } catch { try { Directory.Delete(path, true); } catch { } return null; } } public void DeleteRepository(string name) { Exception e = null; for(int i = 0; i < 3; i++) { try { string path = Path.Combine(Settings.BasePath, name); Directory.Delete(path, true); } catch(Exception ex) { e = ex; } } if (e != null) throw new GitException("Failed to delete repository", e); }
Execute Git commands
Git-upload-pack
Git-receive-pack
The main code is GitCommandResult to implement IActionResult.
public async Task ExecuteResultAsync(ActionContext context) { HttpResponse response = context.HttpContext.Response; Stream responseStream = GetOutputStream(context.HttpContext); string contentType = $"application/x-{Options.Service}"; if (Options.AdvertiseRefs) contentType += "-advertisement"; response.ContentType = contentType; response.Headers.Add("Expires", "Fri, 01 Jan 1980 00:00:00 GMT"); response.Headers.Add("Pragma", "no-cache"); response.Headers.Add("Cache-Control", "no-cache, max-age=0, must-revalidate"); ProcessStartInfo info = new ProcessStartInfo(_gitPath, Options.ToString()) { UseShellExecute = false, CreateNoWindow = true, RedirectStandardInput = true, RedirectStandardOutput = true, RedirectStandardError = true }; using (Process process = Process.Start(info)) { GetInputStream(context.HttpContext).CopyTo(process.StandardInput.BaseStream); if (Options.EndStreamWithNull) process.StandardInput.Write('\0'); process.StandardInput.Dispose(); using (StreamWriter writer = new StreamWriter(responseStream)) { if (Options.AdvertiseRefs) { string service = $"# service={Options.Service}\n"; writer.Write($"{service.Length + 4:x4}{service}0000"); writer.Flush(); } process.StandardOutput.BaseStream.CopyTo(responseStream); } process.WaitForExit(); } }
BasicAuthentication Basic Authentication implementation
Git http performs Basic authentication by default.
In ASP. NET Core 2.0, the Authentication changes significantly. Some code in earlier versions cannot be used.
Implement AuthenticationHandler first, implement AuthenticationSchemeOptions, and create BasicAuthenticationOptions.
These two classes are the most important ones. The following two classes are helper classes for configuration and middleware registration.
For more information, see the official documentation.
Authentication
Https://docs.microsoft.com/zh-cn/aspnet/core/security/authentication/
Https://docs.microsoft.com/zh-cn/aspnet/core/migration/1x-to-2x/identity-2x
1 public class BasicAuthenticationHandler : AuthenticationHandler<BasicAuthenticationOptions> 2 { 3 public BasicAuthenticationHandler(IOptionsMonitor<BasicAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock) 4 : base(options, logger, encoder, clock) 5 { } 6 protected async override Task<AuthenticateResult> HandleAuthenticateAsync() 7 { 8 if (!Request.Headers.ContainsKey("Authorization")) 9 return AuthenticateResult.NoResult();10 11 string authHeader = Request.Headers["Authorization"];12 if (!authHeader.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase))13 return AuthenticateResult.NoResult();14 15 string token = authHeader.Substring("Basic ".Length).Trim();16 string credentialString = Encoding.UTF8.GetString(Convert.FromBase64String(token));17 string[] credentials = credentialString.Split(':');18 19 if (credentials.Length != 2)20 return AuthenticateResult.Fail("More than two strings seperated by colons found");21 22 ClaimsPrincipal principal = await Options.SignInAsync(credentials[0], credentials[1]);23 24 if (principal != null)25 {26 AuthenticationTicket ticket = new AuthenticationTicket(principal, new AuthenticationProperties(), BasicAuthenticationDefaults.AuthenticationScheme);27 return AuthenticateResult.Success(ticket);28 }29 30 return AuthenticateResult.Fail("Wrong credentials supplied");31 }32 protected override Task HandleForbiddenAsync(AuthenticationProperties properties)33 {34 Response.StatusCode = 403;35 return base.HandleForbiddenAsync(properties);36 }37 38 protected override Task HandleChallengeAsync(AuthenticationProperties properties)39 {40 Response.StatusCode = 401;41 string headerValue = $"{BasicAuthenticationDefaults.AuthenticationScheme} realm=\"{Options.Realm}\"";42 Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.WWWAuthenticate, headerValue);43 return base.HandleChallengeAsync(properties);44 }45 }46 47 public class BasicAuthenticationOptions : AuthenticationSchemeOptions, IOptions<BasicAuthenticationOptions>48 {49 private string _realm;50 51 public IServiceCollection ServiceCollection { get; set; }52 public BasicAuthenticationOptions Value => this;53 public string Realm54 {55 get { return _realm; }56 set57 {58 _realm = value;59 }60 }61 62 public async Task<ClaimsPrincipal> SignInAsync(string userName, string password)63 {64 using (var serviceScope = ServiceCollection.BuildServiceProvider().CreateScope())65 {66 var _user = serviceScope.ServiceProvider.GetService<IRepository<User>>();67 var user = _user.List(r => r.Name == userName && r.Password == password).FirstOrDefault();68 if (user == null)69 return null;70 var identity = new ClaimsIdentity(BasicAuthenticationDefaults.AuthenticationScheme, ClaimTypes.Name, ClaimTypes.Role);71 identity.AddClaim(new Claim(ClaimTypes.Name, user.Name));72 var principal = new ClaimsPrincipal(identity);73 return principal;74 }75 }76 }77 78 public static class BasicAuthenticationDefaults79 {80 public const string AuthenticationScheme = "Basic";81 }82 public static class BasicAuthenticationExtensions83 {84 public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder)85 => builder.AddBasic(BasicAuthenticationDefaults.AuthenticationScheme, _ => { _.ServiceCollection = builder.Services;_.Realm = "GitServer"; });86 87 public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, Action<BasicAuthenticationOptions> configureOptions)88 => builder.AddBasic(BasicAuthenticationDefaults.AuthenticationScheme, configureOptions);89 90 public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme, Action<BasicAuthenticationOptions> configureOptions)91 => builder.AddBasic(authenticationScheme, displayName: null, configureOptions: configureOptions);92 93 public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<BasicAuthenticationOptions> configureOptions)94 {95 builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IOptions<BasicAuthenticationOptions>, BasicAuthenticationOptions>());96 return builder.AddScheme<BasicAuthenticationOptions, BasicAuthenticationHandler>(authenticationScheme, displayName, configureOptions);97 }98 }View Code
CookieAuthentication Cookie Authentication
User-Defined logon without identity.
Main Code:
Enable Cookie
Https://github.com/linezero/GitServer/blob/master/GitServer/Startup.cs#L60
services.AddAuthentication(options => { options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; }).AddCookie(options=> { options.AccessDeniedPath = "/User/Login"; options.LoginPath = "/User/Login"; })
Login
Https://github.com/linezero/GitServer/blob/master/GitServer/Controllers/UserController.cs#L34
var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme, ClaimTypes.Name, ClaimTypes.Role); identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Name)); identity.AddClaim(new Claim(ClaimTypes.Name, user.Name)); identity.AddClaim(new Claim(ClaimTypes.Email, user.Email)); var principal = new ClaimsPrincipal(identity); await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
Official Document Introduction: https://docs.microsoft.com/zh-cn/aspnet/core/security/authentication/cookie? Tabs = aspnetcore2x
Deployment description
After the release, configure the database and git directory (which can be an absolute address and command) and git repository directory.
{ "ConnectionStrings": { "ConnectionType": "Sqlite", //Sqlite,MSSQL,MySQL "DefaultConnection": "Filename=gitserver.db" }, "GitSettings": { "BasePath": "D:\\Git", "GitPath": "git" }}
After running the command, register an account, log on to the account to create a repository, and then follow the prompts to perform operations. Then, git push and git pull are all supported.