ASP.NET Core 開源GitServer 實現自己的GitHub
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 的絕對路徑。
目前實現的功能
- 創建倉庫
- 瀏覽倉庫
- git客戶端push pull
- 數據庫支持 SQLite、MSSQL、MySQL
- 支持用戶管理倉庫
更多功能可以查看readme,也歡迎大家貢獻支持。
Git交互
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(); } }
BasicAuthentication 基本認證實現
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
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 Realm 54 { 55 get { return _realm; } 56 set 57 { 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 BasicAuthenticationDefaults 79 { 80 public const string AuthenticationScheme = "Basic"; 81 } 82 public static class BasicAuthenticationExtensions 83 { 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認證
實現自定義登錄,無需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 都可以。
ASP.NET Core 開源GitServer 實現自己的GitHub