zoukankan      html  css  js  c++  java
  • ASP.NET Core DotNetCore 开源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('');
                    process.StandardInput.Dispose();
    
                    using (StreamWriter writer = new StreamWriter(responseStream))
                    {
                        if (Options.AdvertiseRefs)
                        {
                            string service = $"# service={Options.Service}
    ";
                            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     }
    复制代码

    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
    好文要顶 关注我 收藏该文  
    9
    0
     
     
     
    « 上一篇: .NET及.NET Core系统架构
    » 下一篇: Orchard Core一分钟搭建ASP.NET Core CMS
  • 相关阅读:
    网络流(平面图转对偶图)
    666
    期望总结
    docker-1-简介
    22、整合mybatis
    21、整合Druid数据源
    20、Springboot 与数据访问(JDBC/自动配置)
    19、配置嵌入式servlet容器(下)
    18、配置嵌入式servlet容器(2)
    17、配置嵌入式servlet容器(1)
  • 原文地址:https://www.cnblogs.com/webenh/p/11574332.html
Copyright © 2011-2022 走看看