ASP.NET Core 2.0 开源Git HTTP Server,实现类似 GitHub、GitLab。
GitHub:https://github.com/linezero/GitServer
设置
- "GitSettings": {
- "BasePath": "D:\\Git",
- "GitPath": "git"
- }
需要先安装Git,并确保git 命令可以执行,GitPath 可以是 git 的绝对路径。
目前实现的功能
更多功能可以查看readme,也欢迎大家贡献支持。
LibGit2Sharp 用于操作Git库,实现创建读取仓库信息及删除仓库。
以下是主要代码:
- 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);
- }
执行Git命令
git-upload-pack
git-receive-pack
主要代码 GitCommandResult 实现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();
- }
- }
git http 默认的认证为Basic 基本认证,所以这里实现Basic 基本认证。
在ASP.NET Core 2.0 中 Authentication 变化很大之前1.0的一些代码是无法使用。
首先实现 AuthenticationHandler,然后实现 AuthenticationSchemeOptions,创建 BasicAuthenticationOptions。
最主要就是这两个类,下面两个类为辅助类,用于配置和中间件注册。
更多可以查看官方文档
身份验证
https://docs.microsoft.com/zh-cn/aspnet/core/security/authentication/
https://docs.microsoft.com/zh-cn/aspnet/core/migration/1x-to-2x/identity-2x
- public class BasicAuthenticationHandler : AuthenticationHandler<BasicAuthenticationOptions>
- {
- public BasicAuthenticationHandler(IOptionsMonitor<BasicAuthenticationOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
- : base(options, logger, encoder, clock)
- { }
- protected async override Task<AuthenticateResult> HandleAuthenticateAsync()
- {
- if (!Request.Headers.ContainsKey("Authorization"))
- return AuthenticateResult.NoResult();
- string authHeader = Request.Headers["Authorization"];
- if (!authHeader.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase))
- return AuthenticateResult.NoResult();
- string token = authHeader.Substring("Basic ".Length).Trim();
- string credentialString = Encoding.UTF8.GetString(Convert.FromBase64String(token));
- string[] credentials = credentialString.Split(':');
- if (credentials.Length != 2)
- return AuthenticateResult.Fail("More than two strings seperated by colons found");
- ClaimsPrincipal principal = await Options.SignInAsync(credentials[0], credentials[1]);
- if (principal != null)
- {
- AuthenticationTicket ticket = new AuthenticationTicket(principal, new AuthenticationProperties(), BasicAuthenticationDefaults.AuthenticationScheme);
- return AuthenticateResult.Success(ticket);
- }
- return AuthenticateResult.Fail("Wrong credentials supplied");
- }
- protected override Task HandleForbiddenAsync(AuthenticationProperties properties)
- {
- Response.StatusCode = 403;
- return base.HandleForbiddenAsync(properties);
- }
- protected override Task HandleChallengeAsync(AuthenticationProperties properties)
- {
- Response.StatusCode = 401;
- string headerValue = $"{BasicAuthenticationDefaults.AuthenticationScheme} realm=\"{Options.Realm}\"";
- Response.Headers.Append(Microsoft.Net.Http.Headers.HeaderNames.WWWAuthenticate, headerValue);
- return base.HandleChallengeAsync(properties);
- }
- }
- public class BasicAuthenticationOptions : AuthenticationSchemeOptions, IOptions<BasicAuthenticationOptions>
- {
- private string _realm;
- public IServiceCollection ServiceCollection { get; set; }
- public BasicAuthenticationOptions Value => this;
- public string Realm
- {
- get { return _realm; }
- set
- {
- _realm = value;
- }
- }
- public async Task<ClaimsPrincipal> SignInAsync(string userName, string password)
- {
- using (var serviceScope = ServiceCollection.BuildServiceProvider().CreateScope())
- {
- var _user = serviceScope.ServiceProvider.GetService<IRepository<User>>();
- var user = _user.List(r => r.Name == userName && r.Password == password).FirstOrDefault();
- if (user == null)
- return null;
- var identity = new ClaimsIdentity(BasicAuthenticationDefaults.AuthenticationScheme, ClaimTypes.Name, ClaimTypes.Role);
- identity.AddClaim(new Claim(ClaimTypes.Name, user.Name));
- var principal = new ClaimsPrincipal(identity);
- return principal;
- }
- }
- }
- public static class BasicAuthenticationDefaults
- {
- public const string AuthenticationScheme = "Basic";
- }
- public static class BasicAuthenticationExtensions
- {
- public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder)
- => builder.AddBasic(BasicAuthenticationDefaults.AuthenticationScheme, _ => { _.ServiceCollection = builder.Services;_.Realm = "GitServer"; });
- public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, Action<BasicAuthenticationOptions> configureOptions)
- => builder.AddBasic(BasicAuthenticationDefaults.AuthenticationScheme, configureOptions);
- public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme, Action<BasicAuthenticationOptions> configureOptions)
- => builder.AddBasic(authenticationScheme, displayName: null, configureOptions: configureOptions);
- public static AuthenticationBuilder AddBasic(this AuthenticationBuilder builder, string authenticationScheme, string displayName, Action<BasicAuthenticationOptions> configureOptions)
- {
- builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IOptions<BasicAuthenticationOptions>, BasicAuthenticationOptions>());
- return builder.AddScheme<BasicAuthenticationOptions, BasicAuthenticationHandler>(authenticationScheme, displayName, configureOptions);
- }
- }
实现自定义登录,无需identity ,实现注册登录。
主要代码:
启用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";
- })
登录
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);
官方文档介绍:https://docs.microsoft.com/zh-cn/aspnet/core/security/authentication/cookie?tabs=aspnetcore2x
发布后配置数据库及git目录(可以为绝对地址和命令)、git 仓库目录。
- {
- "ConnectionStrings": {
- "ConnectionType": "Sqlite",
- //Sqlite,MSSQL,MySQL
- "DefaultConnection": "Filename=gitserver.db"
- },
- "GitSettings": {
- "BasePath": "D:\\Git",
- "GitPath": "git"
- }
- }
运行后注册账户,登录账户创建仓库,然后根据提示操作,随后git push、git pull 都可以。
来源: http://www.cnblogs.com/linezero/p/gitserver.html