0
0
mirror of https://github.com/go-gitea/gitea.git synced 2026-07-11 17:17:53 +02:00

Merge remote-tracking branch 'upstream/main' into limit-repo-size

This commit is contained in:
DmitryFrolovTri 2023-02-03 14:17:44 +00:00
commit 82dcd0c3cf
216 changed files with 1784 additions and 333 deletions

View File

@ -180,6 +180,11 @@ var (
Name: "raw",
Usage: "Display only the token value",
},
cli.StringFlag{
Name: "scopes",
Value: "",
Usage: "Comma separated list of scopes to apply to access token",
},
},
Action: runGenerateAccessToken,
}
@ -698,9 +703,15 @@ func runGenerateAccessToken(c *cli.Context) error {
return err
}
accessTokenScope, err := auth_model.AccessTokenScope(c.String("scopes")).Normalize()
if err != nil {
return err
}
t := &auth_model.AccessToken{
Name: c.String("token-name"),
UID: user.ID,
Name: c.String("token-name"),
UID: user.ID,
Scope: accessTokenScope,
}
if err := auth_model.NewAccessToken(t); err != nil {

View File

@ -2458,6 +2458,8 @@ ROUTER = console
;LIMIT_SIZE_COMPOSER = -1
;; Maximum size of a Conan upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
;LIMIT_SIZE_CONAN = -1
;; Maximum size of a Conda upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
;LIMIT_SIZE_CONDA = -1
;; Maximum size of a Container upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
;LIMIT_SIZE_CONTAINER = -1
;; Maximum size of a Generic upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)

View File

@ -1214,6 +1214,7 @@ Task queue configuration has been moved to `queue.task`. However, the below conf
- `LIMIT_TOTAL_OWNER_SIZE`: **-1**: Maximum size of packages a single owner can use (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
- `LIMIT_SIZE_COMPOSER`: **-1**: Maximum size of a Composer upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
- `LIMIT_SIZE_CONAN`: **-1**: Maximum size of a Conan upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
- `LIMIT_SIZE_CONDA`: **-1**: Maximum size of a Conda upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
- `LIMIT_SIZE_CONTAINER`: **-1**: Maximum size of a Container upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
- `LIMIT_SIZE_GENERIC`: **-1**: Maximum size of a Generic upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)
- `LIMIT_SIZE_HELM`: **-1**: Maximum size of a Helm upload (`-1` means no limits, format `1000`, `1 MB`, `1 GiB`)

View File

@ -329,3 +329,22 @@ Before activating SSPI single sign-on authentication (SSO) you have to prepare y
- You have added the URL of the web app to the `Local intranet zone`
- The clocks of the server and client should not differ with more than 5 minutes (depends on group policy)
- `Integrated Windows Authentication` should be enabled in Internet Explorer (under `Advanced settings`)
## Reverse Proxy
Gitea supports Reverse Proxy Header authentication, it will read headers as a trusted login user name or user email address. This hasn't been enabled by default, you can enable it with
```ini
[service]
ENABLE_REVERSE_PROXY_AUTHENTICATION = true
```
The default login user name is in the `X-WEBAUTH-USER` header, you can change it via changing `REVERSE_PROXY_AUTHENTICATION_USER` in app.ini. If the user doesn't exist, you can enable automatic registration with `ENABLE_REVERSE_PROXY_AUTO_REGISTRATION=true`.
The default login user email is `X-WEBAUTH-EMAIL`, you can change it via changing `REVERSE_PROXY_AUTHENTICATION_EMAIL` in app.ini, this could also be disabled with `ENABLE_REVERSE_PROXY_EMAIL`
If set `ENABLE_REVERSE_PROXY_FULL_NAME=true`, a user full name expected in `X-WEBAUTH-FULLNAME` will be assigned to the user when auto creating the user. You can also change the header name with `REVERSE_PROXY_AUTHENTICATION_FULL_NAME`.
You can also limit the reverse proxy's IP address range with `REVERSE_PROXY_TRUSTED_PROXIES` which default value is `127.0.0.0/8,::1/128`. By `REVERSE_PROXY_LIMIT`, you can limit trusted proxies level.
Notice: Reverse Proxy Auth doesn't support the API. You still need an access token or basic auth to make API requests.

View File

@ -15,4 +15,21 @@ menu:
# 认证
## TBD
## 反向代理认证
Gitea 支持通过读取反向代理传递的 HTTP 头中的登录名或者 email 地址来支持反向代理来认证。默认是不启用的,你可以用以下配置启用。
```ini
[service]
ENABLE_REVERSE_PROXY_AUTHENTICATION = true
```
默认的登录用户名的 HTTP 头是 `X-WEBAUTH-USER`,你可以通过修改 `REVERSE_PROXY_AUTHENTICATION_USER` 来变更它。如果用户不存在,可以自动创建用户,当然你需要修改 `ENABLE_REVERSE_PROXY_AUTO_REGISTRATION=true` 来启用它。
默认的登录用户 Email 的 HTTP 头是 `X-WEBAUTH-EMAIL`,你可以通过修改 `REVERSE_PROXY_AUTHENTICATION_EMAIL` 来变更它。如果用户不存在,可以自动创建用户,当然你需要修改 `ENABLE_REVERSE_PROXY_AUTO_REGISTRATION=true` 来启用它。你也可以通过修改 `ENABLE_REVERSE_PROXY_EMAIL` 来启用或停用这个 HTTP 头。
如果设置了 `ENABLE_REVERSE_PROXY_FULL_NAME=true`,则用户的全名会从 `X-WEBAUTH-FULLNAME` 读取,这样在自动创建用户时将使用这个字段作为用户全名,你也可以通过修改 `REVERSE_PROXY_AUTHENTICATION_FULL_NAME` 来变更 HTTP 头。
你也可以通过修改 `REVERSE_PROXY_TRUSTED_PROXIES` 来设置反向代理的IP地址范围加强安全性默认值是 `127.0.0.0/8,::1/128`。 通过 `REVERSE_PROXY_LIMIT` 可以设置最多信任几级反向代理。
注意:反向代理认证不支持认证 APIAPI 仍旧需要用 access token 来进行认证。

View File

@ -0,0 +1,85 @@
---
date: "2022-12-28T00:00:00+00:00"
title: "Conda Packages Repository"
slug: "packages/conda"
draft: false
toc: false
menu:
sidebar:
parent: "packages"
name: "Conda"
weight: 25
identifier: "conda"
---
# Conda Packages Repository
Publish [Conda](https://docs.conda.io/en/latest/) packages for your user or organization.
**Table of Contents**
{{< toc >}}
## Requirements
To work with the Conda package registry, you need to use [conda](https://docs.conda.io/projects/conda/en/stable/user-guide/install/index.html).
## Configuring the package registry
To register the package registry and provide credentials, edit your `.condarc` file:
```yaml
channel_alias: https://gitea.example.com/api/packages/{owner}/conda
channels:
- https://gitea.example.com/api/packages/{owner}/conda
default_channels:
- https://gitea.example.com/api/packages/{owner}/conda
```
| Placeholder | Description |
| ------------ | ----------- |
| `owner` | The owner of the package. |
See the [official documentation](https://conda.io/projects/conda/en/latest/user-guide/configuration/use-condarc.html) for explanations of the individual settings.
If you need to provide credentials, you may embed them as part of the channel url (`https://user:password@gitea.example.com/...`).
## Publish a package
To publish a package, perform a HTTP PUT operation with the package content in the request body.
```
PUT https://gitea.example.com/api/packages/{owner}/conda/{channel}/{filename}
```
| Placeholder | Description |
| ------------ | ----------- |
| `owner` | The owner of the package. |
| `channel` | The [channel](https://conda.io/projects/conda/en/latest/user-guide/concepts/channels.html) of the package. (optional) |
| `filename` | The name of the file. |
Example request using HTTP Basic authentication:
```shell
curl --user your_username:your_password_or_token \
--upload-file path/to/package-1.0.conda \
https://gitea.example.com/api/packages/testuser/conda/package-1.0.conda
```
You cannot publish a package if a package of the same name and version already exists. You must delete the existing package first.
## Install a package
To install a package from the package registry, execute one of the following commands:
```shell
conda install {package_name}
conda install {package_name}={package_version}
conda install -c {channel} {package_name}
```
| Parameter | Description |
| ----------------- | ----------- |
| `package_name` | The package name. |
| `package_version` | The package version. |
| `channel` | The channel of the package. (optional) |

View File

@ -28,6 +28,7 @@ The following package managers are currently supported:
| ---- | -------- | -------------- |
| [Composer]({{< relref "doc/packages/composer.en-us.md" >}}) | PHP | `composer` |
| [Conan]({{< relref "doc/packages/conan.en-us.md" >}}) | C++ | `conan` |
| [Conda]({{< relref "doc/packages/conda.en-us.md" >}}) | - | `conda` |
| [Container]({{< relref "doc/packages/container.en-us.md" >}}) | - | any OCI compliant client |
| [Generic]({{< relref "doc/packages/generic.en-us.md" >}}) | - | any HTTP client |
| [Helm]({{< relref "doc/packages/helm.en-us.md" >}}) | - | any HTTP client, `cm-push` |

2
go.mod
View File

@ -26,6 +26,7 @@ require (
github.com/dimiro1/reply v0.0.0-20200315094148-d0136a4c9e21
github.com/djherbis/buffer v1.2.0
github.com/djherbis/nio/v3 v3.0.1
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5
github.com/dustin/go-humanize v1.0.0
github.com/editorconfig/editorconfig-core-go/v2 v2.5.1
github.com/emersion/go-imap v1.2.1
@ -161,7 +162,6 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/dlclark/regexp2 v1.7.0 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/emersion/go-sasl v0.0.0-20200509203442-7bfe0ed36a21 // indirect
github.com/fatih/color v1.13.0 // indirect
github.com/felixge/httpsnoop v1.0.3 // indirect

View File

@ -24,6 +24,9 @@ func (err ErrKeyUnableVerify) Error() string {
return fmt.Sprintf("Unable to verify key content [result: %s]", err.Result)
}
// ErrKeyIsPrivate is returned when the provided key is a private key not a public key
var ErrKeyIsPrivate = util.NewSilentWrapErrorf(util.ErrInvalidArgument, "the provided key is a private key")
// ErrKeyNotExist represents a "KeyNotExist" kind of error.
type ErrKeyNotExist struct {
ID int64

View File

@ -96,6 +96,9 @@ func parseKeyString(content string) (string, error) {
if block == nil {
return "", fmt.Errorf("failed to parse PEM block containing the public key")
}
if strings.Contains(block.Type, "PRIVATE") {
return "", ErrKeyIsPrivate
}
pub, err := x509.ParsePKIXPublicKey(block.Bytes)
if err != nil {

View File

@ -10,6 +10,35 @@ import (
"code.gitea.io/gitea/models/db"
)
/*
The reasons behind the DBFS (database-filesystem) package:
When a Gitea action is running, the Gitea action server should collect and store all the logs.
The requirements are:
* The running logs must be stored across the cluster if the Gitea servers are deployed as a cluster.
* The logs will be archived to Object Storage (S3/MinIO, etc.) after a period of time.
* The Gitea action UI should be able to render the running logs and the archived logs.
Some possible solutions for the running logs:
* [Not ideal] Using local temp file: it can not be shared across the cluster.
* [Not ideal] Using shared file in the filesystem of git repository: although at the moment, the Gitea cluster's
git repositories must be stored in a shared filesystem, in the future, Gitea may need a dedicated Git Service Server
to decouple the shared filesystem. Then the action logs will become a blocker.
* [Not ideal] Record the logs in a database table line by line: it has a couple of problems:
- It's difficult to make multiple increasing sequence (log line number) for different databases.
- The database table will have a lot of rows and be affected by the big-table performance problem.
- It's difficult to load logs by using the same interface as other storages.
- It's difficult to calculate the size of the logs.
The DBFS solution:
* It can be used in a cluster.
* It can share the same interface (Read/Write/Seek) as other storages.
* It's very friendly to database because it only needs to store much fewer rows than the log-line solution.
* In the future, when Gitea action needs to limit the log size (other CI/CD services also do so), it's easier to calculate the log file size.
* Even sometimes the UI needs to render the tailing lines, the tailing lines can be found be counting the "\n" from the end of the file by seek.
The seeking and finding is not the fastest way, but it's still acceptable and won't affect the performance too much.
*/
type dbfsMeta struct {
ID int64 `xorm:"pk autoincr"`
FullPath string `xorm:"VARCHAR(500) UNIQUE NOT NULL"`

View File

@ -4,6 +4,7 @@
owner_name: user2
lower_name: repo1
name: repo1
default_branch: master
num_watches: 4
num_stars: 0
num_forks: 0
@ -34,6 +35,7 @@
owner_name: user2
lower_name: repo2
name: repo2
default_branch: master
num_watches: 0
num_stars: 1
num_forks: 0
@ -64,6 +66,7 @@
owner_name: user3
lower_name: repo3
name: repo3
default_branch: master
num_watches: 0
num_stars: 0
num_forks: 0
@ -94,6 +97,7 @@
owner_name: user5
lower_name: repo4
name: repo4
default_branch: master
num_watches: 0
num_stars: 1
num_forks: 0
@ -274,6 +278,7 @@
owner_name: user12
lower_name: repo10
name: repo10
default_branch: master
num_watches: 0
num_stars: 0
num_forks: 1
@ -304,6 +309,7 @@
owner_name: user13
lower_name: repo11
name: repo11
default_branch: master
num_watches: 0
num_stars: 0
num_forks: 0
@ -425,6 +431,7 @@
owner_name: user2
lower_name: repo15
name: repo15
default_branch: master
num_watches: 0
num_stars: 0
num_forks: 0
@ -455,6 +462,7 @@
owner_name: user2
lower_name: repo16
name: repo16
default_branch: master
num_watches: 0
num_stars: 0
num_forks: 0
@ -905,6 +913,7 @@
owner_name: user2
lower_name: repo20
name: repo20
default_branch: master
num_watches: 0
num_stars: 0
num_forks: 0
@ -965,6 +974,7 @@
owner_name: user2
lower_name: utf8
name: utf8
default_branch: master
num_watches: 0
num_stars: 0
num_forks: 0
@ -1055,6 +1065,7 @@
owner_name: user2
lower_name: commits_search_test
name: commits_search_test
default_branch: master
num_watches: 0
num_stars: 0
num_forks: 0
@ -1085,6 +1096,7 @@
owner_name: user2
lower_name: git_hooks_test
name: git_hooks_test
default_branch: master
num_watches: 0
num_stars: 0
num_forks: 0
@ -1115,6 +1127,7 @@
owner_name: limited_org
lower_name: public_repo_on_limited_org
name: public_repo_on_limited_org
default_branch: master
num_watches: 0
num_stars: 0
num_forks: 0
@ -1145,6 +1158,7 @@
owner_name: limited_org
lower_name: private_repo_on_limited_org
name: private_repo_on_limited_org
default_branch: master
num_watches: 0
num_stars: 0
num_forks: 0
@ -1175,6 +1189,7 @@
owner_name: privated_org
lower_name: public_repo_on_private_org
name: public_repo_on_private_org
default_branch: master
num_watches: 0
num_stars: 0
num_forks: 0
@ -1205,6 +1220,7 @@
owner_name: privated_org
lower_name: private_repo_on_private_org
name: private_repo_on_private_org
default_branch: master
num_watches: 0
num_stars: 0
num_forks: 0
@ -1235,6 +1251,7 @@
owner_name: user2
lower_name: glob
name: glob
default_branch: master
num_watches: 0
num_stars: 0
num_forks: 0
@ -1295,6 +1312,7 @@
owner_name: user27
lower_name: template1
name: template1
default_branch: master
num_watches: 0
num_stars: 0
num_forks: 0
@ -1355,6 +1373,7 @@
owner_name: org26
lower_name: repo_external_tracker
name: repo_external_tracker
default_branch: master
num_watches: 0
num_stars: 0
num_forks: 0
@ -1385,6 +1404,7 @@
owner_name: org26
lower_name: repo_external_tracker_numeric
name: repo_external_tracker_numeric
default_branch: master
num_watches: 0
num_stars: 0
num_forks: 0
@ -1415,6 +1435,7 @@
owner_name: org26
lower_name: repo_external_tracker_alpha
name: repo_external_tracker_alpha
default_branch: master
num_watches: 0
num_stars: 0
num_forks: 0
@ -1445,6 +1466,7 @@
owner_name: user27
lower_name: repo49
name: repo49
default_branch: master
num_watches: 0
num_stars: 0
num_forks: 0
@ -1475,6 +1497,7 @@
owner_name: user30
lower_name: repo50
name: repo50
default_branch: master
num_watches: 0
num_stars: 0
num_forks: 0
@ -1505,6 +1528,7 @@
owner_name: user30
lower_name: repo51
name: repo51
default_branch: master
num_watches: 0
num_stars: 0
num_forks: 0
@ -1565,6 +1589,7 @@
owner_name: user30
lower_name: renderer
name: renderer
default_branch: master
is_archived: false
is_empty: false
is_private: false
@ -1592,6 +1617,7 @@
owner_name: user2
lower_name: lfs
name: lfs
default_branch: master
is_empty: false
is_archived: false
is_private: true

View File

@ -173,8 +173,9 @@ func (prs PullRequestList) loadAttributes(ctx context.Context) error {
for i := range issues {
set[issues[i].ID] = issues[i]
}
for i := range prs {
prs[i].Issue = set[prs[i].IssueID]
for _, pr := range prs {
pr.Issue = set[pr.IssueID]
pr.Issue.PullRequest = pr // panic here means issueIDs and prs are not in sync
}
return nil
}

View File

@ -0,0 +1,63 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package conda
import (
"context"
"strings"
"code.gitea.io/gitea/models/db"
"code.gitea.io/gitea/models/packages"
conda_module "code.gitea.io/gitea/modules/packages/conda"
"xorm.io/builder"
)
type FileSearchOptions struct {
OwnerID int64
Channel string
Subdir string
Filename string
}
// SearchFiles gets all files matching the search options
func SearchFiles(ctx context.Context, opts *FileSearchOptions) ([]*packages.PackageFile, error) {
var cond builder.Cond = builder.Eq{
"package.type": packages.TypeConda,
"package.owner_id": opts.OwnerID,
"package_version.is_internal": false,
}
if opts.Filename != "" {
cond = cond.And(builder.Eq{
"package_file.lower_name": strings.ToLower(opts.Filename),
})
}
var versionPropsCond builder.Cond = builder.Eq{
"package_property.ref_type": packages.PropertyTypePackage,
"package_property.name": conda_module.PropertyChannel,
"package_property.value": opts.Channel,
}
cond = cond.And(builder.In("package.id", builder.Select("package_property.ref_id").Where(versionPropsCond).From("package_property")))
var filePropsCond builder.Cond = builder.Eq{
"package_property.ref_type": packages.PropertyTypeFile,
"package_property.name": conda_module.PropertySubdir,
"package_property.value": opts.Subdir,
}
cond = cond.And(builder.In("package_file.id", builder.Select("package_property.ref_id").Where(filePropsCond).From("package_property")))
sess := db.GetEngine(ctx).
Select("package_file.*").
Table("package_file").
Join("INNER", "package_version", "package_version.id = package_file.version_id").
Join("INNER", "package", "package.id = package_version.package_id").
Where(cond)
pfs := make([]*packages.PackageFile, 0, 10)
return pfs, sess.Find(&pfs)
}

View File

@ -13,6 +13,7 @@ import (
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/packages/composer"
"code.gitea.io/gitea/modules/packages/conan"
"code.gitea.io/gitea/modules/packages/conda"
"code.gitea.io/gitea/modules/packages/container"
"code.gitea.io/gitea/modules/packages/helm"
"code.gitea.io/gitea/modules/packages/maven"
@ -132,6 +133,8 @@ func GetPackageDescriptor(ctx context.Context, pv *PackageVersion) (*PackageDesc
metadata = &composer.Metadata{}
case TypeConan:
metadata = &conan.Metadata{}
case TypeConda:
metadata = &conda.VersionMetadata{}
case TypeContainer:
metadata = &container.Metadata{}
case TypeGeneric:

View File

@ -32,6 +32,7 @@ type Type string
const (
TypeComposer Type = "composer"
TypeConan Type = "conan"
TypeConda Type = "conda"
TypeContainer Type = "container"
TypeGeneric Type = "generic"
TypeHelm Type = "helm"
@ -47,6 +48,7 @@ const (
var TypeList = []Type{
TypeComposer,
TypeConan,
TypeConda,
TypeContainer,
TypeGeneric,
TypeHelm,
@ -66,6 +68,8 @@ func (pt Type) Name() string {
return "Composer"
case TypeConan:
return "Conan"
case TypeConda:
return "Conda"
case TypeContainer:
return "Container"
case TypeGeneric:
@ -97,6 +101,8 @@ func (pt Type) SVGName() string {
return "gitea-composer"
case TypeConan:
return "gitea-conan"
case TypeConda:
return "gitea-conda"
case TypeContainer:
return "octicon-container"
case TypeGeneric:

View File

@ -228,11 +228,6 @@ func (repo *Repository) IsBroken() bool {
// AfterLoad is invoked from XORM after setting the values of all fields of this object.
func (repo *Repository) AfterLoad() {
// FIXME: use models migration to solve all at once.
if len(repo.DefaultBranch) == 0 {
repo.DefaultBranch = setting.Repository.DefaultBranch
}
repo.NumOpenIssues = repo.NumIssues - repo.NumClosedIssues
repo.NumOpenPulls = repo.NumPulls - repo.NumClosedPulls
repo.NumOpenMilestones = repo.NumMilestones - repo.NumClosedMilestones

View File

@ -75,7 +75,6 @@ func DetectWorkflows(commit *git.Commit, triggedEvent webhook_module.HookEventTy
if evt.Name != triggedEvent.Event() {
continue
}
if detectMatched(commit, triggedEvent, payload, evt) {
workflows[entry.Name()] = content
}
@ -105,8 +104,9 @@ func detectMatched(commit *git.Commit, triggedEvent webhook_module.HookEventType
for cond, vals := range evt.Acts {
switch cond {
case "branches", "tags":
refShortName := git.RefName(pushPayload.Ref).ShortName()
for _, val := range vals {
if glob.MustCompile(val, '/').Match(pushPayload.Ref) {
if glob.MustCompile(val, '/').Match(refShortName) {
matchTimes++
break
}
@ -160,8 +160,9 @@ func detectMatched(commit *git.Commit, triggedEvent webhook_module.HookEventType
}
}
case "branches":
refShortName := git.RefName(prPayload.PullRequest.Base.Ref).ShortName()
for _, val := range vals {
if glob.MustCompile(val, '/').Match(prPayload.PullRequest.Base.Ref) {
if glob.MustCompile(val, '/').Match(refShortName) {
matchTimes++
break
}

View File

@ -44,7 +44,7 @@ func EscapeControlReader(reader io.Reader, writer io.Writer, locale translation.
return streamer.escaped, err
}
// EscapeControlStringReader escapes the unicode control sequences in a provided reader of string content and writer in a locale and returns the findings as an EscapeStatus and the escaped []byte
// EscapeControlStringReader escapes the unicode control sequences in a provided reader of string content and writer in a locale and returns the findings as an EscapeStatus and the escaped []byte. HTML line breaks are not inserted after every newline by this method.
func EscapeControlStringReader(reader io.Reader, writer io.Writer, locale translation.Locale, allowed ...rune) (escaped *EscapeStatus, err error) {
bufRd := bufio.NewReader(reader)
outputStream := &HTMLStreamerWriter{Writer: writer}
@ -65,10 +65,6 @@ func EscapeControlStringReader(reader io.Reader, writer io.Writer, locale transl
}
break
}
if err := streamer.SelfClosingTag("br"); err != nil {
streamer.escaped.HasError = true
return streamer.escaped, err
}
}
return streamer.escaped, err
}

View File

@ -19,14 +19,16 @@ import (
func AddCacheControlToHeader(h http.Header, maxAge time.Duration, additionalDirectives ...string) {
directives := make([]string, 0, 2+len(additionalDirectives))
// "max-age=0 + must-revalidate" (aka "no-cache") is preferred instead of "no-store"
// because browsers may restore some input fields after navigate-back / reload a page.
if setting.IsProd {
if maxAge == 0 {
directives = append(directives, "no-store")
directives = append(directives, "max-age=0", "private", "must-revalidate")
} else {
directives = append(directives, "private", "max-age="+strconv.Itoa(int(maxAge.Seconds())))
}
} else {
directives = append(directives, "no-store")
directives = append(directives, "max-age=0", "private", "must-revalidate")
// to remind users they are using non-prod setting.
h.Add("X-Gitea-Debug", "RUN_MODE="+setting.RunMode)

View File

@ -0,0 +1,243 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package conda
import (
"archive/tar"
"archive/zip"
"compress/bzip2"
"io"
"strings"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/validation"
"github.com/klauspost/compress/zstd"
)
var (
ErrInvalidStructure = util.SilentWrap{Message: "package structure is invalid", Err: util.ErrInvalidArgument}
ErrInvalidName = util.SilentWrap{Message: "package name is invalid", Err: util.ErrInvalidArgument}
ErrInvalidVersion = util.SilentWrap{Message: "package version is invalid", Err: util.ErrInvalidArgument}
)
const (
PropertyName = "conda.name"
PropertyChannel = "conda.channel"
PropertySubdir = "conda.subdir"
PropertyMetadata = "conda.metdata"
)
// Package represents a Conda package
type Package struct {
Name string
Version string
Subdir string
VersionMetadata *VersionMetadata
FileMetadata *FileMetadata
}
// VersionMetadata represents the metadata of a Conda package
type VersionMetadata struct {
Description string `json:"description,omitempty"`
Summary string `json:"summary,omitempty"`
ProjectURL string `json:"project_url,omitempty"`
RepositoryURL string `json:"repository_url,omitempty"`
DocumentationURL string `json:"documentation_url,omitempty"`
License string `json:"license,omitempty"`
LicenseFamily string `json:"license_family,omitempty"`
}
// FileMetadata represents the metadata of a Conda package file
type FileMetadata struct {
IsCondaPackage bool `json:"is_conda"`
Architecture string `json:"architecture,omitempty"`
NoArch string `json:"noarch,omitempty"`
Build string `json:"build,omitempty"`
BuildNumber int64 `json:"build_number,omitempty"`
Dependencies []string `json:"dependencies,omitempty"`
Platform string `json:"platform,omitempty"`
Timestamp int64 `json:"timestamp,omitempty"`
}
type index struct {
Name string `json:"name"`
Version string `json:"version"`
Architecture string `json:"arch"`
NoArch string `json:"noarch"`
Build string `json:"build"`
BuildNumber int64 `json:"build_number"`
Dependencies []string `json:"depends"`
License string `json:"license"`
LicenseFamily string `json:"license_family"`
Platform string `json:"platform"`
Subdir string `json:"subdir"`
Timestamp int64 `json:"timestamp"`
}
type about struct {
Description string `json:"description"`
Summary string `json:"summary"`
ProjectURL string `json:"home"`
RepositoryURL string `json:"dev_url"`
DocumentationURL string `json:"doc_url"`
}
type ReaderAndReaderAt interface {
io.Reader
io.ReaderAt
}
// ParsePackageBZ2 parses the Conda package file compressed with bzip2
func ParsePackageBZ2(r io.Reader) (*Package, error) {
gzr := bzip2.NewReader(r)
return parsePackageTar(gzr)
}
// ParsePackageConda parses the Conda package file compressed with zip and zstd
func ParsePackageConda(r io.ReaderAt, size int64) (*Package, error) {
zr, err := zip.NewReader(r, size)
if err != nil {
return nil, err
}
for _, file := range zr.File {
if strings.HasPrefix(file.Name, "info-") && strings.HasSuffix(file.Name, ".tar.zst") {
f, err := zr.Open(file.Name)
if err != nil {
return nil, err
}
defer f.Close()
dec, err := zstd.NewReader(f)
if err != nil {
return nil, err
}
defer dec.Close()
p, err := parsePackageTar(dec)
if p != nil {
p.FileMetadata.IsCondaPackage = true
}
return p, err
}
}
return nil, ErrInvalidStructure
}
func parsePackageTar(r io.Reader) (*Package, error) {
var i *index
var a *about
tr := tar.NewReader(r)
for {
hdr, err := tr.Next()
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
if hdr.Typeflag != tar.TypeReg {
continue
}
if hdr.Name == "info/index.json" {
if err := json.NewDecoder(tr).Decode(&i); err != nil {
return nil, err
}
if !checkName(i.Name) {
return nil, ErrInvalidName
}
if !checkVersion(i.Version) {
return nil, ErrInvalidVersion
}
if a != nil {
break // stop loop if both files were found
}
} else if hdr.Name == "info/about.json" {
if err := json.NewDecoder(tr).Decode(&a); err != nil {
return nil, err
}
if !validation.IsValidURL(a.ProjectURL) {
a.ProjectURL = ""
}
if !validation.IsValidURL(a.RepositoryURL) {
a.RepositoryURL = ""
}
if !validation.IsValidURL(a.DocumentationURL) {
a.DocumentationURL = ""
}
if i != nil {
break // stop loop if both files were found
}
}
}
if i == nil {
return nil, ErrInvalidStructure
}
if a == nil {
a = &about{}
}
return &Package{
Name: i.Name,
Version: i.Version,
Subdir: i.Subdir,
VersionMetadata: &VersionMetadata{
License: i.License,
LicenseFamily: i.LicenseFamily,
Description: a.Description,
Summary: a.Summary,
ProjectURL: a.ProjectURL,
RepositoryURL: a.RepositoryURL,
DocumentationURL: a.DocumentationURL,
},
FileMetadata: &FileMetadata{
Architecture: i.Architecture,
NoArch: i.NoArch,
Build: i.Build,
BuildNumber: i.BuildNumber,
Dependencies: i.Dependencies,
Platform: i.Platform,
Timestamp: i.Timestamp,
},
}, nil
}
// https://github.com/conda/conda-build/blob/db9a728a9e4e6cfc895637ca3221117970fc2663/conda_build/metadata.py#L1393
func checkName(name string) bool {
if name == "" {
return false
}
if name != strings.ToLower(name) {
return false
}
return !checkBadCharacters(name, "!")
}
// https://github.com/conda/conda-build/blob/db9a728a9e4e6cfc895637ca3221117970fc2663/conda_build/metadata.py#L1403
func checkVersion(version string) bool {
if version == "" {
return false
}
return !checkBadCharacters(version, "-")
}
func checkBadCharacters(s, additional string) bool {
if strings.ContainsAny(s, "=@#$%^&*:;\"'\\|<>?/ ") {
return true
}
return strings.ContainsAny(s, additional)
}

View File

@ -0,0 +1,150 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package conda
import (
"archive/tar"
"archive/zip"
"bytes"
"io"
"testing"
"github.com/dsnet/compress/bzip2"
"github.com/klauspost/compress/zstd"
"github.com/stretchr/testify/assert"
)
const (
packageName = "gitea"
packageVersion = "1.0.1"
description = "Package Description"
projectURL = "https://gitea.io"
repositoryURL = "https://gitea.io/gitea/gitea"
documentationURL = "https://docs.gitea.io"
)
func TestParsePackage(t *testing.T) {
createArchive := func(files map[string][]byte) *bytes.Buffer {
var buf bytes.Buffer
tw := tar.NewWriter(&buf)
for filename, content := range files {
hdr := &tar.Header{
Name: filename,
Mode: 0o600,
Size: int64(len(content)),
}
tw.WriteHeader(hdr)
tw.Write(content)
}
tw.Close()
return &buf
}
t.Run("MissingIndexFile", func(t *testing.T) {
buf := createArchive(map[string][]byte{"dummy.txt": {}})
p, err := parsePackageTar(buf)
assert.Nil(t, p)
assert.ErrorIs(t, err, ErrInvalidStructure)
})
t.Run("MissingAboutFile", func(t *testing.T) {
buf := createArchive(map[string][]byte{"info/index.json": []byte(`{"name":"name","version":"1.0"}`)})
p, err := parsePackageTar(buf)
assert.NotNil(t, p)
assert.NoError(t, err)
assert.Equal(t, "name", p.Name)
assert.Equal(t, "1.0", p.Version)
assert.Empty(t, p.VersionMetadata.ProjectURL)
})
t.Run("InvalidName", func(t *testing.T) {
for _, name := range []string{"", "name!", "nAMe"} {
buf := createArchive(map[string][]byte{"info/index.json": []byte(`{"name":"` + name + `","version":"1.0"}`)})
p, err := parsePackageTar(buf)
assert.Nil(t, p)
assert.ErrorIs(t, err, ErrInvalidName)
}
})
t.Run("InvalidVersion", func(t *testing.T) {
for _, version := range []string{"", "1.0-2"} {
buf := createArchive(map[string][]byte{"info/index.json": []byte(`{"name":"name","version":"` + version + `"}`)})
p, err := parsePackageTar(buf)
assert.Nil(t, p)
assert.ErrorIs(t, err, ErrInvalidVersion)
}
})
t.Run("Valid", func(t *testing.T) {
buf := createArchive(map[string][]byte{
"info/index.json": []byte(`{"name":"` + packageName + `","version":"` + packageVersion + `","subdir":"linux-64"}`),
"info/about.json": []byte(`{"description":"` + description + `","dev_url":"` + repositoryURL + `","doc_url":"` + documentationURL + `","home":"` + projectURL + `"}`),
})
p, err := parsePackageTar(buf)
assert.NotNil(t, p)
assert.NoError(t, err)
assert.Equal(t, packageName, p.Name)
assert.Equal(t, packageVersion, p.Version)
assert.Equal(t, "linux-64", p.Subdir)
assert.Equal(t, description, p.VersionMetadata.Description)
assert.Equal(t, projectURL, p.VersionMetadata.ProjectURL)
assert.Equal(t, repositoryURL, p.VersionMetadata.RepositoryURL)
assert.Equal(t, documentationURL, p.VersionMetadata.DocumentationURL)
})
t.Run(".tar.bz2", func(t *testing.T) {
tarArchive := createArchive(map[string][]byte{
"info/index.json": []byte(`{"name":"` + packageName + `","version":"` + packageVersion + `"}`),
})
var buf bytes.Buffer
bw, _ := bzip2.NewWriter(&buf, nil)
io.Copy(bw, tarArchive)
bw.Close()
br := bytes.NewReader(buf.Bytes())
p, err := ParsePackageBZ2(br)
assert.NotNil(t, p)
assert.NoError(t, err)
assert.Equal(t, packageName, p.Name)
assert.Equal(t, packageVersion, p.Version)
assert.False(t, p.FileMetadata.IsCondaPackage)
})
t.Run(".conda", func(t *testing.T) {
tarArchive := createArchive(map[string][]byte{
"info/index.json": []byte(`{"name":"` + packageName + `","version":"` + packageVersion + `"}`),
})
var infoBuf bytes.Buffer
zsw, _ := zstd.NewWriter(&infoBuf)
io.Copy(zsw, tarArchive)
zsw.Close()
var buf bytes.Buffer
zpw := zip.NewWriter(&buf)
w, _ := zpw.Create("info-x.tar.zst")
w.Write(infoBuf.Bytes())
zpw.Close()
br := bytes.NewReader(buf.Bytes())
p, err := ParsePackageConda(br, int64(br.Len()))
assert.NotNil(t, p)
assert.NoError(t, err)
assert.Equal(t, packageName, p.Name)
assert.Equal(t, packageVersion, p.Version)
assert.True(t, p.FileMetadata.IsCondaPackage)
})
}

View File

@ -27,6 +27,7 @@ var (
LimitTotalOwnerSize int64
LimitSizeComposer int64
LimitSizeConan int64
LimitSizeConda int64
LimitSizeContainer int64
LimitSizeGeneric int64
LimitSizeHelm int64
@ -66,6 +67,7 @@ func newPackages() {
Packages.LimitTotalOwnerSize = mustBytes(sec, "LIMIT_TOTAL_OWNER_SIZE")
Packages.LimitSizeComposer = mustBytes(sec, "LIMIT_SIZE_COMPOSER")
Packages.LimitSizeConan = mustBytes(sec, "LIMIT_SIZE_CONAN")
Packages.LimitSizeConda = mustBytes(sec, "LIMIT_SIZE_CONDA")
Packages.LimitSizeContainer = mustBytes(sec, "LIMIT_SIZE_CONTAINER")
Packages.LimitSizeGeneric = mustBytes(sec, "LIMIT_SIZE_GENERIC")
Packages.LimitSizeHelm = mustBytes(sec, "LIMIT_SIZE_HELM")

View File

@ -518,6 +518,7 @@ organization_leave_success = You have successfully left the organization %s.
invalid_ssh_key = Cannot verify your SSH key: %s
invalid_gpg_key = Cannot verify your GPG key: %s
invalid_ssh_principal = Invalid principal: %s
must_use_public_key = The key you provided is a private key. Please do not upload your private key anywhere. Use your public key instead.
unable_verify_ssh_key = "Cannot verify the SSH key; double-check it for mistakes."
auth_failed = Authentication failed: %v
@ -3162,6 +3163,11 @@ conan.details.repository = Repository
conan.registry = Setup this registry from the command line:
conan.install = To install the package using Conan, run the following command:
conan.documentation = For more information on the Conan registry, see <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/conan/">the documentation</a>.
conda.registry = Setup this registry as a Conda repository in your <code>.condarc</code> file:
conda.install = To install the package using Conda, run the following command:
conda.documentation = For more information on the Conda registry, see <a target="_blank" rel="noopener noreferrer" href="https://docs.gitea.io/en-us/packages/conda/">the documentation</a>.
conda.details.repository_site = Repository Site
conda.details.documentation_site = Documentation Site
container.details.type = Image Type
container.details.platform = Platform
container.details.repository_site = Repository Site

View File

@ -0,0 +1 @@
<svg viewBox="0 0 32 32" class="svg gitea-conda" width="16" height="16" aria-hidden="true"><path fill="#43b02a" fill-rule="evenodd" stroke="#43b02a" stroke-width=".068" d="M16.559 8.137a7.2 7.2 0 0 0-1.234-1.708 7.586 7.586 0 0 0-.19 2.183 5.161 5.161 0 0 1 1.424-.475ZM13.617 9.466a7.992 7.992 0 0 0-1.993-1.2 8.123 8.123 0 0 0 .885 2.183c0 .063.443-.475 1.108-.981ZM17.445 7.188a9.143 9.143 0 0 1 1.3-2.246A7.585 7.585 0 0 0 17 2.854a8.35 8.35 0 0 0-1.3 2.278 8.451 8.451 0 0 1 1.74 2.056ZM11.592 11.744a10.276 10.276 0 0 0-2.692-.158 7.478 7.478 0 0 0 1.93 1.9 6.858 6.858 0 0 1 .759-1.74zM6.878 15.161a7.44 7.44 0 0 1 2.942-1.139 10.019 10.019 0 0 1-2.056-2.278 7.639 7.639 0 0 0-2.847 1.2 7.11 7.11 0 0 0 1.961 2.215zM10.516 14.876a6.16 6.16 0 0 0-2.815.886 9.936 9.936 0 0 0 2.815 1.2 7.683 7.683 0 0 1 0-2.088zM14.281 5.543A7.839 7.839 0 0 0 11.592 4.4 8.361 8.361 0 0 0 11.4 7a8.875 8.875 0 0 1 2.47 1.264 10.292 10.292 0 0 1 .411-2.721ZM24.025 3.234a20.488 20.488 0 0 1 .917 4.112 6.823 6.823 0 0 0-3.068 1.519 7.443 7.443 0 0 1 1.55 1.044 1.351 1.351 0 0 0 1.645.316 36.938 36.938 0 0 0 2.721-2.72 1.273 1.273 0 0 0-.159-1.835 20.521 20.521 0 0 0-3.606-2.436ZM4.379 12.06a8.67 8.67 0 0 1 2.847-1.26 7.763 7.763 0 0 1-.759-2.974 14.687 14.687 0 0 0-2.088 4.234ZM11.339 10.668a9.991 9.991 0 0 1-.949-2.784 7.928 7.928 0 0 0-2.911-.126 7.312 7.312 0 0 0 .791 2.879 9.664 9.664 0 0 1 3.069.031ZM6.119 15.73a8.894 8.894 0 0 1-2.025-2.373 14.208 14.208 0 0 0-.063 4.9 8.522 8.522 0 0 1 2.088-2.527Z"/><path fill="#43b02a" fill-rule="evenodd" stroke="#43b02a" stroke-width=".068" d="M22.538 3.487A7.581 7.581 0 0 0 20.323 5.1a11.789 11.789 0 0 1 .823 2.5 9.775 9.775 0 0 1 2.309-1.329 6.593 6.593 0 0 0-.917-2.784ZM19.374 6.3a8.608 8.608 0 0 0-.822 1.676 9.645 9.645 0 0 1 1.329.19 7.568 7.568 0 0 0-.507-1.866ZM19.659 3.9a9.577 9.577 0 0 1 2.056-1.487A15.38 15.38 0 0 0 18.046 2a9.709 9.709 0 0 1 1.613 1.9Z"/><path fill="#43b02a" d="M27.378 23.892c-1.993-1.9-2.4-3.132-4.081-1.835a7.837 7.837 0 0 1-12.591-4.144A10.179 10.179 0 0 1 6.878 16.3a9.427 9.427 0 0 0-2.562 3.321h-.032C7.163 30.5 21.178 33.035 27.663 26.233c1.076-1.139.095-1.933-.285-2.341ZM6.309 20.855a7.559 7.559 0 0 1 .917-2.025 6.872 6.872 0 0 0 2.151.538c1.013 2.689 4.556 6.264 8.922 6.264a9.632 9.632 0 0 0 6.3-2.309 12.841 12.841 0 0 1 1.772 1.771c.095.127.095.159.095.159-5.766 5.03-15.538 4.302-20.157-4.398Z"/><path fill="#43b02a" fill-rule="evenodd" stroke="#43b02a" stroke-width=".067" d="M10.67 4.11a19.934 19.934 0 0 0-.214 2.509 10.512 10.512 0 0 0-2.689-.093A18 18 0 0 1 10.67 4.11ZM12.26 3.274a9.107 9.107 0 0 1 2.445 1.053 14.083 14.083 0 0 1 1.253-2.137 12.106 12.106 0 0 0-3.698 1.084z"/></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -16,6 +16,7 @@ import (
"code.gitea.io/gitea/modules/web"
"code.gitea.io/gitea/routers/api/packages/composer"
"code.gitea.io/gitea/routers/api/packages/conan"
"code.gitea.io/gitea/routers/api/packages/conda"
"code.gitea.io/gitea/routers/api/packages/container"
"code.gitea.io/gitea/routers/api/packages/generic"
"code.gitea.io/gitea/routers/api/packages/helm"
@ -167,6 +168,43 @@ func CommonRoutes(ctx gocontext.Context) *web.Route {
})
})
}, reqPackageAccess(perm.AccessModeRead))
r.Group("/conda", func() {
var (
downloadPattern = regexp.MustCompile(`\A(.+/)?(.+)/((?:[^/]+(?:\.tar\.bz2|\.conda))|(?:current_)?repodata\.json(?:\.bz2)?)\z`)
uploadPattern = regexp.MustCompile(`\A(.+/)?([^/]+(?:\.tar\.bz2|\.conda))\z`)
)
r.Get("/*", func(ctx *context.Context) {
m := downloadPattern.FindStringSubmatch(ctx.Params("*"))
if len(m) == 0 {
ctx.Status(http.StatusNotFound)
return
}
ctx.SetParams("channel", strings.TrimSuffix(m[1], "/"))
ctx.SetParams("architecture", m[2])
ctx.SetParams("filename", m[3])
switch m[3] {
case "repodata.json", "repodata.json.bz2", "current_repodata.json", "current_repodata.json.bz2":
conda.EnumeratePackages(ctx)
default:
conda.DownloadPackageFile(ctx)
}
})
r.Put("/*", reqPackageAccess(perm.AccessModeWrite), func(ctx *context.Context) {
m := uploadPattern.FindStringSubmatch(ctx.Params("*"))
if len(m) == 0 {
ctx.Status(http.StatusNotFound)
return
}
ctx.SetParams("channel", strings.TrimSuffix(m[1], "/"))
ctx.SetParams("filename", m[2])
conda.UploadPackageFile(ctx)
})
}, reqPackageAccess(perm.AccessModeRead))
r.Group("/generic", func() {
r.Group("/{packagename}/{packageversion}", func() {
r.Delete("", reqPackageAccess(perm.AccessModeWrite), generic.DeletePackage)

View File

@ -0,0 +1,306 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package conda
import (
"errors"
"fmt"
"io"
"net/http"
"strings"
packages_model "code.gitea.io/gitea/models/packages"
conda_model "code.gitea.io/gitea/models/packages/conda"
"code.gitea.io/gitea/modules/context"
"code.gitea.io/gitea/modules/json"
"code.gitea.io/gitea/modules/log"
packages_module "code.gitea.io/gitea/modules/packages"
conda_module "code.gitea.io/gitea/modules/packages/conda"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/routers/api/packages/helper"
packages_service "code.gitea.io/gitea/services/packages"
"github.com/dsnet/compress/bzip2"
)
func apiError(ctx *context.Context, status int, obj interface{}) {
helper.LogAndProcessError(ctx, status, obj, func(message string) {
ctx.JSON(status, struct {
Reason string `json:"reason"`
Message string `json:"message"`
}{
Reason: http.StatusText(status),
Message: message,
})
})
}
func EnumeratePackages(ctx *context.Context) {
type Info struct {
Subdir string `json:"subdir"`
}
type PackageInfo struct {
Name string `json:"name"`
Version string `json:"version"`
NoArch string `json:"noarch"`
Subdir string `json:"subdir"`
Timestamp int64 `json:"timestamp"`
Build string `json:"build"`
BuildNumber int64 `json:"build_number"`
Dependencies []string `json:"depends"`
License string `json:"license"`
LicenseFamily string `json:"license_family"`
HashMD5 string `json:"md5"`
HashSHA256 string `json:"sha256"`
Size int64 `json:"size"`
}
type RepoData struct {
Info Info `json:"info"`
Packages map[string]*PackageInfo `json:"packages"`
PackagesConda map[string]*PackageInfo `json:"packages.conda"`
Removed map[string]*PackageInfo `json:"removed"`
}
repoData := &RepoData{
Info: Info{
Subdir: ctx.Params("architecture"),
},
Packages: make(map[string]*PackageInfo),
PackagesConda: make(map[string]*PackageInfo),
Removed: make(map[string]*PackageInfo),
}
pfs, err := conda_model.SearchFiles(ctx, &conda_model.FileSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Channel: ctx.Params("channel"),
Subdir: repoData.Info.Subdir,
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
if len(pfs) == 0 {
apiError(ctx, http.StatusNotFound, nil)
return
}
pds := make(map[int64]*packages_model.PackageDescriptor)
for _, pf := range pfs {
pd, exists := pds[pf.VersionID]
if !exists {
pv, err := packages_model.GetVersionByID(ctx, pf.VersionID)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
pd, err = packages_model.GetPackageDescriptor(ctx, pv)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
pds[pf.VersionID] = pd
}
var pfd *packages_model.PackageFileDescriptor
for _, d := range pd.Files {
if d.File.ID == pf.ID {
pfd = d
break
}
}
var fileMetadata *conda_module.FileMetadata
if err := json.Unmarshal([]byte(pfd.Properties.GetByName(conda_module.PropertyMetadata)), &fileMetadata); err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
versionMetadata := pd.Metadata.(*conda_module.VersionMetadata)
pi := &PackageInfo{
Name: pd.PackageProperties.GetByName(conda_module.PropertyName),
Version: pd.Version.Version,
NoArch: fileMetadata.NoArch,
Subdir: repoData.Info.Subdir,
Timestamp: fileMetadata.Timestamp,
Build: fileMetadata.Build,
BuildNumber: fileMetadata.BuildNumber,
Dependencies: fileMetadata.Dependencies,
License: versionMetadata.License,
LicenseFamily: versionMetadata.LicenseFamily,
HashMD5: pfd.Blob.HashMD5,
HashSHA256: pfd.Blob.HashSHA256,
Size: pfd.Blob.Size,
}
if fileMetadata.IsCondaPackage {
repoData.PackagesConda[pfd.File.Name] = pi
} else {
repoData.Packages[pfd.File.Name] = pi
}
}
resp := ctx.Resp
var w io.Writer = resp
if strings.HasSuffix(ctx.Params("filename"), ".json") {
resp.Header().Set("Content-Type", "application/json")
} else {
resp.Header().Set("Content-Type", "application/x-bzip2")
zw, err := bzip2.NewWriter(w, nil)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
defer zw.Close()
w = zw
}
resp.WriteHeader(http.StatusOK)
if err := json.NewEncoder(w).Encode(repoData); err != nil {
log.Error("JSON encode: %v", err)
}
}
func UploadPackageFile(ctx *context.Context) {
upload, close, err := ctx.UploadStream()
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
if close {
defer upload.Close()
}
buf, err := packages_module.CreateHashedBufferFromReader(upload, 32*1024*1024)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
defer buf.Close()
var pck *conda_module.Package
if strings.HasSuffix(strings.ToLower(ctx.Params("filename")), ".tar.bz2") {
pck, err = conda_module.ParsePackageBZ2(buf)
} else {
pck, err = conda_module.ParsePackageConda(buf, buf.Size())
}
if err != nil {
if errors.Is(err, util.ErrInvalidArgument) {
apiError(ctx, http.StatusBadRequest, err)
} else {
apiError(ctx, http.StatusInternalServerError, err)
}
return
}
if _, err := buf.Seek(0, io.SeekStart); err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
fullName := pck.Name
channel := ctx.Params("channel")
if channel != "" {
fullName = channel + "/" + pck.Name
}
extension := ".tar.bz2"
if pck.FileMetadata.IsCondaPackage {
extension = ".conda"
}
fileMetadataRaw, err := json.Marshal(pck.FileMetadata)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
_, _, err = packages_service.CreatePackageOrAddFileToExisting(
&packages_service.PackageCreationInfo{
PackageInfo: packages_service.PackageInfo{
Owner: ctx.Package.Owner,
PackageType: packages_model.TypeConda,
Name: fullName,
Version: pck.Version,
},
SemverCompatible: false,
Creator: ctx.Doer,
Metadata: pck.VersionMetadata,
PackageProperties: map[string]string{
conda_module.PropertyName: pck.Name,
conda_module.PropertyChannel: channel,
},
},
&packages_service.PackageFileCreationInfo{
PackageFileInfo: packages_service.PackageFileInfo{
Filename: fmt.Sprintf("%s-%s-%s%s", pck.Name, pck.Version, pck.FileMetadata.Build, extension),
CompositeKey: pck.Subdir,
},
Creator: ctx.Doer,
Data: buf,
IsLead: true,
Properties: map[string]string{
conda_module.PropertySubdir: pck.Subdir,
conda_module.PropertyMetadata: string(fileMetadataRaw),
},
},
)
if err != nil {
switch err {
case packages_model.ErrDuplicatePackageFile:
apiError(ctx, http.StatusConflict, err)
case packages_service.ErrQuotaTotalCount, packages_service.ErrQuotaTypeSize, packages_service.ErrQuotaTotalSize:
apiError(ctx, http.StatusForbidden, err)
default:
apiError(ctx, http.StatusInternalServerError, err)
}
return
}
ctx.Status(http.StatusCreated)
}
func DownloadPackageFile(ctx *context.Context) {
pfs, err := conda_model.SearchFiles(ctx, &conda_model.FileSearchOptions{
OwnerID: ctx.Package.Owner.ID,
Channel: ctx.Params("channel"),
Subdir: ctx.Params("architecture"),
Filename: ctx.Params("filename"),
})
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
if len(pfs) != 1 {
apiError(ctx, http.StatusNotFound, nil)
return
}
pf := pfs[0]
s, _, err := packages_service.GetPackageFileStream(ctx, pf)
if err != nil {
apiError(ctx, http.StatusInternalServerError, err)
return
}
defer s.Close()
ctx.ServeContent(s, &context.ServeHeaderOptions{
Filename: pf.Name,
LastModified: pf.CreatedUnix.AsLocalTime(),
})
}

View File

@ -40,7 +40,7 @@ func ListPackages(ctx *context.APIContext) {
// in: query
// description: package type filter
// type: string
// enum: [composer, conan, container, generic, helm, maven, npm, nuget, pub, pypi, rubygems, vagrant]
// enum: [composer, conan, conda, container, generic, helm, maven, npm, nuget, pub, pypi, rubygems, vagrant]
// - name: q
// in: query
// description: name filter

View File

@ -22,6 +22,7 @@ const (
func DefaultOrSystemWebhooks(ctx *context.Context) {
var err error
ctx.Data["Title"] = ctx.Tr("admin.hooks")
ctx.Data["PageIsAdminSystemHooks"] = true
ctx.Data["PageIsAdminDefaultHooks"] = true

View File

@ -328,6 +328,14 @@ func NewRelease(ctx *context.Context) {
}
}
ctx.Data["IsAttachmentEnabled"] = setting.Attachment.Enabled
var err error
// Get assignees.
ctx.Data["Assignees"], err = repo_model.GetRepoAssignees(ctx, ctx.Repo.Repository)
if err != nil {
ctx.ServerError("GetAssignees", err)
return
}
upload.AddUploadContext(ctx, "release")
ctx.HTML(http.StatusOK, tplReleaseNew)
}
@ -484,6 +492,13 @@ func EditRelease(ctx *context.Context) {
}
ctx.Data["attachments"] = rel.Attachments
// Get assignees.
ctx.Data["Assignees"], err = repo_model.GetRepoAssignees(ctx, rel.Repo)
if err != nil {
ctx.ServerError("GetAssignees", err)
return
}
ctx.HTML(http.StatusOK, tplReleaseNew)
}

View File

@ -60,7 +60,7 @@ const (
// SettingsCtxData is a middleware that sets all the general context data for the
// settings template.
func SettingsCtxData(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["Title"] = ctx.Tr("repo.settings.options")
ctx.Data["PageIsSettingsOptions"] = true
ctx.Data["ForcePrivate"] = setting.Repository.ForcePrivate
ctx.Data["MirrorsEnabled"] = setting.Mirror.Enabled
@ -894,7 +894,7 @@ func handleSettingRemoteAddrError(ctx *context.Context, err error, form *forms.R
// Collaboration render a repository's collaboration page
func Collaboration(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["Title"] = ctx.Tr("repo.settings.collaboration")
ctx.Data["PageIsSettingsCollaboration"] = true
users, err := repo_model.GetCollaborators(ctx, ctx.Repo.Repository.ID, db.ListOptions{})
@ -1134,7 +1134,7 @@ func GitHooksEditPost(ctx *context.Context) {
// DeployKeys render the deploy keys list of a repository page
func DeployKeys(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys")
ctx.Data["Title"] = ctx.Tr("repo.settings.deploy_keys") + " / " + ctx.Tr("secrets.secrets")
ctx.Data["PageIsSettingsKeys"] = true
ctx.Data["DisableSSH"] = setting.SSH.Disabled
@ -1173,6 +1173,10 @@ func DeployKeysPost(ctx *context.Context) {
ctx.Flash.Info(ctx.Tr("settings.ssh_disabled"))
} else if asymkey_model.IsErrKeyUnableVerify(err) {
ctx.Flash.Info(ctx.Tr("form.unable_verify_ssh_key"))
} else if err == asymkey_model.ErrKeyIsPrivate {
ctx.Data["HasError"] = true
ctx.Data["Err_Content"] = true
ctx.Flash.Error(ctx.Tr("form.must_use_public_key"))
} else {
ctx.Data["HasError"] = true
ctx.Data["Err_Content"] = true

View File

@ -31,7 +31,7 @@ const (
// ProtectedBranchRules render the page to protect the repository
func ProtectedBranchRules(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["Title"] = ctx.Tr("repo.settings.branches")
ctx.Data["PageIsSettingsBranches"] = true
rules, err := git_model.FindRepoProtectedBranchRules(ctx, ctx.Repo.Repository.ID)
@ -46,7 +46,7 @@ func ProtectedBranchRules(ctx *context.Context) {
// SetDefaultBranchPost set default branch
func SetDefaultBranchPost(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["Title"] = ctx.Tr("repo.settings.branches.update_default_branch")
ctx.Data["PageIsSettingsBranches"] = true
repo := ctx.Repo.Repository

View File

@ -133,7 +133,7 @@ func DeleteProtectedTagPost(ctx *context.Context) {
}
func setTagsContext(ctx *context.Context) error {
ctx.Data["Title"] = ctx.Tr("repo.settings")
ctx.Data["Title"] = ctx.Tr("repo.settings.tags")
ctx.Data["PageIsSettingsTags"] = true
protectedTags, err := git_model.GetProtectedTags(ctx, ctx.Repo.Repository.ID)

View File

@ -30,7 +30,7 @@ const (
// Account renders change user's password, user's email and user suicide page
func Account(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["Title"] = ctx.Tr("settings.account")
ctx.Data["PageIsSettingsAccount"] = true
ctx.Data["Email"] = ctx.Doer.Email
ctx.Data["EnableNotifyMail"] = setting.Service.EnableNotifyMail

View File

@ -17,7 +17,7 @@ import (
// AdoptOrDeleteRepository adopts or deletes a repository
func AdoptOrDeleteRepository(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["Title"] = ctx.Tr("settings.adopt")
ctx.Data["PageIsSettingsRepos"] = true
allowAdopt := ctx.IsUserSiteAdmin() || setting.Repository.AllowAdoptionOfUnadoptedRepositories
ctx.Data["allowAdopt"] = allowAdopt

View File

@ -21,7 +21,7 @@ const (
// Applications render manage access token page
func Applications(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["Title"] = ctx.Tr("settings.applications")
ctx.Data["PageIsSettingsApplications"] = true
loadApplicationsData(ctx)

View File

@ -23,7 +23,7 @@ const (
// Keys render user's SSH/GPG public keys page
func Keys(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["Title"] = ctx.Tr("settings.ssh_gpg_keys")
ctx.Data["PageIsSettingsKeys"] = true
ctx.Data["DisableSSH"] = setting.SSH.Disabled
ctx.Data["BuiltinSSH"] = setting.SSH.StartBuiltinServer
@ -159,6 +159,8 @@ func KeysPost(ctx *context.Context) {
ctx.Flash.Info(ctx.Tr("settings.ssh_disabled"))
} else if asymkey_model.IsErrKeyUnableVerify(err) {
ctx.Flash.Info(ctx.Tr("form.unable_verify_ssh_key"))
} else if err == asymkey_model.ErrKeyIsPrivate {
ctx.Flash.Error(ctx.Tr("form.must_use_public_key"))
} else {
ctx.Flash.Error(ctx.Tr("form.invalid_ssh_key", err.Error()))
}

View File

@ -42,7 +42,7 @@ const (
// Profile render user's profile page
func Profile(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["Title"] = ctx.Tr("settings.profile")
ctx.Data["PageIsSettingsProfile"] = true
ctx.Data["AllowedUserVisibilityModes"] = setting.Service.AllowedUserVisibilityModesSlice.ToVisibleTypeSlice()
@ -219,7 +219,7 @@ func DeleteAvatar(ctx *context.Context) {
// Organization render all the organization of the user
func Organization(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["Title"] = ctx.Tr("settings.organization")
ctx.Data["PageIsSettingsOrganization"] = true
opts := organization.FindOrgOptions{
@ -254,7 +254,7 @@ func Organization(ctx *context.Context) {
// Repos display a list of all repositories of the user
func Repos(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["Title"] = ctx.Tr("settings.repos")
ctx.Data["PageIsSettingsRepos"] = true
ctx.Data["allowAdopt"] = ctx.IsUserSiteAdmin() || setting.Repository.AllowAdoptionOfUnadoptedRepositories
ctx.Data["allowDelete"] = ctx.IsUserSiteAdmin() || setting.Repository.AllowDeleteOfUnadoptedRepositories
@ -360,7 +360,7 @@ func Repos(ctx *context.Context) {
// Appearance render user's appearance settings
func Appearance(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["Title"] = ctx.Tr("settings.appearance")
ctx.Data["PageIsSettingsAppearance"] = true
var hiddenCommentTypes *big.Int

View File

@ -22,7 +22,7 @@ const (
// Security render change user's password page and 2FA
func Security(ctx *context.Context) {
ctx.Data["Title"] = ctx.Tr("settings")
ctx.Data["Title"] = ctx.Tr("settings.security")
ctx.Data["PageIsSettingsSecurity"] = true
if ctx.FormString("openid.return_to") != "" {

View File

@ -196,22 +196,39 @@ func checkRestricted(l *ldap.Conn, ls *Source, userDN string) bool {
}
// List all group memberships of a user
func (source *Source) listLdapGroupMemberships(l *ldap.Conn, uid string) []string {
func (source *Source) listLdapGroupMemberships(l *ldap.Conn, uid string, applyGroupFilter bool) []string {
var ldapGroups []string
groupFilter := fmt.Sprintf("(%s=%s)", source.GroupMemberUID, ldap.EscapeFilter(uid))
var searchFilter string
groupFilter, ok := source.sanitizedGroupFilter(source.GroupFilter)
if !ok {
return ldapGroups
}
groupDN, ok := source.sanitizedGroupDN(source.GroupDN)
if !ok {
return ldapGroups
}
if applyGroupFilter {
searchFilter = fmt.Sprintf("(&(%s)(%s=%s))", groupFilter, source.GroupMemberUID, ldap.EscapeFilter(uid))
} else {
searchFilter = fmt.Sprintf("(%s=%s)", source.GroupMemberUID, ldap.EscapeFilter(uid))
}
result, err := l.Search(ldap.NewSearchRequest(
source.GroupDN,
groupDN,
ldap.ScopeWholeSubtree,
ldap.NeverDerefAliases,
0,
0,
false,
groupFilter,
searchFilter,
[]string{},
nil,
))
if err != nil {
log.Error("Failed group search using filter[%s]: %v", groupFilter, err)
log.Error("Failed group search in LDAP with filter [%s]: %v", searchFilter, err)
return ldapGroups
}
@ -238,9 +255,7 @@ func (source *Source) mapLdapGroupsToTeams() map[string]map[string][]string {
}
// getMappedMemberships : returns the organizations and teams to modify the users membership
func (source *Source) getMappedMemberships(l *ldap.Conn, uid string) (map[string][]string, map[string][]string) {
// get all LDAP group memberships for user
usersLdapGroups := source.listLdapGroupMemberships(l, uid)
func (source *Source) getMappedMemberships(usersLdapGroups []string, uid string) (map[string][]string, map[string][]string) {
// unmarshall LDAP group team map from configs
ldapGroupsToTeams := source.mapLdapGroupsToTeams()
membershipsToAdd := map[string][]string{}
@ -260,6 +275,14 @@ func (source *Source) getMappedMemberships(l *ldap.Conn, uid string) (map[string
return membershipsToAdd, membershipsToRemove
}
func (source *Source) getUserAttributeListedInGroup(entry *ldap.Entry) string {
if strings.ToLower(source.UserUID) == "dn" {
return entry.DN
}
return entry.GetAttributeValue(source.UserUID)
}
// SearchEntry : search an LDAP source if an entry (name, passwd) is valid and in the specific filter
func (source *Source) SearchEntry(name, passwd string, directBind bool) *SearchResult {
// See https://tools.ietf.org/search/rfc4513#section-5.1.2
@ -375,58 +398,30 @@ func (source *Source) SearchEntry(name, passwd string, directBind bool) *SearchR
firstname := sr.Entries[0].GetAttributeValue(source.AttributeName)
surname := sr.Entries[0].GetAttributeValue(source.AttributeSurname)
mail := sr.Entries[0].GetAttributeValue(source.AttributeMail)
uid := sr.Entries[0].GetAttributeValue(source.UserUID)
if source.UserUID == "dn" || source.UserUID == "DN" {
uid = sr.Entries[0].DN
}
teamsToAdd := make(map[string][]string)
teamsToRemove := make(map[string][]string)
// Check group membership
if source.GroupsEnabled && source.GroupFilter != "" {
groupFilter, ok := source.sanitizedGroupFilter(source.GroupFilter)
if !ok {
return nil
}
groupDN, ok := source.sanitizedGroupDN(source.GroupDN)
if !ok {
if source.GroupsEnabled {
userAttributeListedInGroup := source.getUserAttributeListedInGroup(sr.Entries[0])
usersLdapGroups := source.listLdapGroupMemberships(l, userAttributeListedInGroup, true)
if source.GroupFilter != "" && len(usersLdapGroups) == 0 {
return nil
}
log.Trace("Fetching groups '%v' with filter '%s' and base '%s'", source.GroupMemberUID, groupFilter, groupDN)
groupSearch := ldap.NewSearchRequest(
groupDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, groupFilter,
[]string{source.GroupMemberUID},
nil)
srg, err := l.Search(groupSearch)
if err != nil {
log.Error("LDAP group search failed: %v", err)
return nil
} else if len(srg.Entries) < 1 {
log.Error("LDAP group search failed: 0 entries")
return nil
}
isMember := false
Entries:
for _, group := range srg.Entries {
for _, member := range group.GetAttributeValues(source.GroupMemberUID) {
if (source.UserUID == "dn" && member == sr.Entries[0].DN) || member == uid {
isMember = true
break Entries
}
}
}
if !isMember {
log.Error("LDAP group membership test failed")
return nil
if source.GroupTeamMap != "" || source.GroupTeamMapRemoval {
teamsToAdd, teamsToRemove = source.getMappedMemberships(usersLdapGroups, userAttributeListedInGroup)
}
}
if isAttributeSSHPublicKeySet {
sshPublicKey = sr.Entries[0].GetAttributeValues(source.AttributeSSHPublicKey)
}
isAdmin := checkAdmin(l, source, userDN)
var isRestricted bool
if !isAdmin {
isRestricted = checkRestricted(l, source, userDN)
@ -436,12 +431,6 @@ func (source *Source) SearchEntry(name, passwd string, directBind bool) *SearchR
Avatar = sr.Entries[0].GetRawAttributeValue(source.AttributeAvatar)
}
teamsToAdd := make(map[string][]string)
teamsToRemove := make(map[string][]string)
if source.GroupsEnabled && (source.GroupTeamMap != "" || source.GroupTeamMapRemoval) {
teamsToAdd, teamsToRemove = source.getMappedMemberships(l, uid)
}
if !directBind && source.AttributesInBind {
// binds user (checking password) after looking-up attributes in BindDN context
err = bindUser(l, userDN, passwd)
@ -520,19 +509,29 @@ func (source *Source) SearchEntries() ([]*SearchResult, error) {
return nil, err
}
result := make([]*SearchResult, len(sr.Entries))
result := make([]*SearchResult, 0, len(sr.Entries))
for i, v := range sr.Entries {
for _, v := range sr.Entries {
teamsToAdd := make(map[string][]string)
teamsToRemove := make(map[string][]string)
if source.GroupsEnabled && (source.GroupTeamMap != "" || source.GroupTeamMapRemoval) {
userAttributeListedInGroup := v.GetAttributeValue(source.UserUID)
if source.UserUID == "dn" || source.UserUID == "DN" {
userAttributeListedInGroup = v.DN
if source.GroupsEnabled {
userAttributeListedInGroup := source.getUserAttributeListedInGroup(v)
if source.GroupFilter != "" {
usersLdapGroups := source.listLdapGroupMemberships(l, userAttributeListedInGroup, true)
if len(usersLdapGroups) == 0 {
continue
}
}
if source.GroupTeamMap != "" || source.GroupTeamMapRemoval {
usersLdapGroups := source.listLdapGroupMemberships(l, userAttributeListedInGroup, false)
teamsToAdd, teamsToRemove = source.getMappedMemberships(usersLdapGroups, userAttributeListedInGroup)
}
teamsToAdd, teamsToRemove = source.getMappedMemberships(l, userAttributeListedInGroup)
}
result[i] = &SearchResult{
user := &SearchResult{
Username: v.GetAttributeValue(source.AttributeUsername),
Name: v.GetAttributeValue(source.AttributeName),
Surname: v.GetAttributeValue(source.AttributeSurname),
@ -541,16 +540,22 @@ func (source *Source) SearchEntries() ([]*SearchResult, error) {
LdapTeamAdd: teamsToAdd,
LdapTeamRemove: teamsToRemove,
}
if !result[i].IsAdmin {
result[i].IsRestricted = checkRestricted(l, source, v.DN)
if !user.IsAdmin {
user.IsRestricted = checkRestricted(l, source, v.DN)
}
if isAttributeSSHPublicKeySet {
result[i].SSHPublicKey = v.GetAttributeValues(source.AttributeSSHPublicKey)
user.SSHPublicKey = v.GetAttributeValues(source.AttributeSSHPublicKey)
}
if isAtributeAvatarSet {
result[i].Avatar = v.GetRawAttributeValue(source.AttributeAvatar)
user.Avatar = v.GetRawAttributeValue(source.AttributeAvatar)
}
result[i].LowerName = strings.ToLower(result[i].Username)
user.LowerName = strings.ToLower(user.Username)
result = append(result, user)
}
return result, nil

View File

@ -15,7 +15,7 @@ import (
type PackageCleanupRuleForm struct {
ID int64
Enabled bool
Type string `binding:"Required;In(composer,conan,container,generic,helm,maven,npm,nuget,pub,pypi,rubygems,vagrant)"`
Type string `binding:"Required;In(composer,conan,conda,container,generic,helm,maven,npm,nuget,pub,pypi,rubygems,vagrant)"`
KeepCount int `binding:"In(0,1,5,10,25,50,100)"`
KeepPattern string `binding:"RegexPattern"`
RemoveDays int `binding:"In(0,7,14,30,60,90,180)"`

View File

@ -339,6 +339,8 @@ func CheckSizeQuotaExceeded(ctx context.Context, doer, owner *user_model.User, p
typeSpecificSize = setting.Packages.LimitSizeComposer
case packages_model.TypeConan:
typeSpecificSize = setting.Packages.LimitSizeConan
case packages_model.TypeConda:
typeSpecificSize = setting.Packages.LimitSizeConda
case packages_model.TypeContainer:
typeSpecificSize = setting.Packages.LimitSizeContainer
case packages_model.TypeGeneric:

View File

@ -298,7 +298,6 @@ func AddTestPullRequestTask(doer *user_model.User, repoID int64, branch string,
}
}
pr.Issue.PullRequest = pr
notification.NotifyPullRequestSynchronized(ctx, doer, pr)
}
}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content admin config">
<div role="main" aria-label="{{.Title}}" class="page-content admin config">
{{template "admin/navbar" .}}
<div class="ui container">
<div class="twelve wide column content">

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content admin config">
<div role="main" aria-label="{{.Title}}" class="page-content admin config">
{{template "admin/navbar" .}}
{{template "user/settings/applications_oauth2_edit_form" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content admin edit authentication">
<div role="main" aria-label="{{.Title}}" class="page-content admin edit authentication">
{{template "admin/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content admin authentication">
<div role="main" aria-label="{{.Title}}" class="page-content admin authentication">
{{template "admin/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content admin new authentication">
<div role="main" aria-label="{{.Title}}" class="page-content admin new authentication">
{{template "admin/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content admin config">
<div role="main" aria-label="{{.Title}}" class="page-content admin config">
{{template "admin/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content admin dashboard">
<div role="main" aria-label="{{.Title}}" class="page-content admin dashboard">
{{template "admin/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content admin user">
<div role="main" aria-label="{{.Title}}" class="page-content admin user">
{{template "admin/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content admin settings new webhook">
<div role="main" aria-label="{{.Title}}" class="page-content admin settings new webhook">
{{template "admin/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content admin hooks">
<div role="main" aria-label="{{.Title}}" class="page-content admin hooks">
{{template "admin/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content admin monitor">
<div role="main" aria-label="{{.Title}}" class="page-content admin monitor">
{{template "admin/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content admin notice">
<div role="main" aria-label="{{.Title}}" class="page-content admin notice">
{{template "admin/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content admin user">
<div role="main" aria-label="{{.Title}}" class="page-content admin user">
{{template "admin/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content admin user">
<div role="main" aria-label="{{.Title}}" class="page-content admin user">
{{template "admin/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content admin monitor">
<div role="main" aria-label="{{.Title}}" class="page-content admin monitor">
{{template "admin/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content admin user">
<div role="main" aria-label="{{.Title}}" class="page-content admin user">
{{template "admin/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content admin user">
<div role="main" aria-label="{{.Title}}" class="page-content admin user">
{{template "admin/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content admin monitor">
<div role="main" aria-label="{{.Title}}" class="page-content admin monitor">
{{template "admin/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content admin edit user">
<div role="main" aria-label="{{.Title}}" class="page-content admin edit user">
{{template "admin/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content admin user">
<div role="main" aria-label="{{.Title}}" class="page-content admin user">
{{template "admin/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content admin new user">
<div role="main" aria-label="{{.Title}}" class="page-content admin new user">
{{template "admin/navbar" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content explore users">
<div role="main" aria-label="{{.Title}}" class="page-content explore users">
{{template "explore/navbar" .}}
<div class="ui container">
{{template "code/searchform" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content explore users">
<div role="main" aria-label="{{.Title}}" class="page-content explore users">
{{template "explore/navbar" .}}
<div class="ui container">
{{template "explore/search" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content explore repositories">
<div role="main" aria-label="{{.Title}}" class="page-content explore repositories">
{{template "explore/navbar" .}}
<div class="ui container">
{{template "explore/repo_search" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content explore users">
<div role="main" aria-label="{{.Title}}" class="page-content explore users">
{{template "explore/navbar" .}}
<div class="ui container">
{{template "explore/search" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content home">
<div role="main" aria-label="{{if .IsSigned}}{{.locale.Tr "dashboard"}}{{else}}{{.locale.Tr "home"}}{{end}}" class="page-content home">
<div class="ui stackable middle very relaxed page grid">
<div class="sixteen wide center aligned centered column">
<div>

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content install">
<div role="main" aria-label="{{.Title}}" class="page-content install">
<div class="ui middle very relaxed page grid">
<div class="sixteen wide center aligned centered column">
<h3 class="ui top attached header">

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content organization new org">
<div role="main" aria-label="{{.Title}}" class="page-content organization new org">
<div class="ui middle very relaxed page grid">
<div class="column">
<form class="ui form" action="{{.Link}}" method="post">

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content organization profile">
<div role="main" aria-label="{{.Title}}" class="page-content organization profile">
<div class="ui container df">
{{avatar .Org 140 "org-avatar"}}
<div id="org-info">

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content organization members">
<div role="main" aria-label="{{.Title}}" class="page-content organization members">
{{template "org/header" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content repository packages">
<div role="main" aria-label="{{.Title}}" class="page-content repository packages">
{{template "user/overview/header" .}}
{{template "projects/list" .}}
</div>

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content repository packages">
<div role="main" aria-label="{{.Title}}" class="page-content repository packages">
{{template "user/overview/header" .}}
{{template "projects/new" .}}
</div>

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content repository packages">
<div role="main" aria-label="{{.Title}}" class="page-content repository packages">
{{template "user/overview/header" .}}
{{template "projects/view" .}}
</div>

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content organization settings options">
<div role="main" aria-label="{{.Title}}" class="page-content organization settings options">
{{template "org/header" .}}
<div class="ui container">
<div class="ui grid">

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content organization settings options">
<div role="main" aria-label="{{.Title}}" class="page-content organization settings options">
{{template "org/header" .}}
{{template "user/settings/applications_oauth2_edit_form" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content organization settings delete">
<div role="main" aria-label="{{.Title}}" class="page-content organization settings delete">
{{template "org/header" .}}
<div class="ui container">
<div class="ui grid">

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content organization settings new webhook">
<div role="main" aria-label="{{.Title}}" class="page-content organization settings new webhook">
{{template "org/header" .}}
<div class="ui container">
<div class="ui grid">

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content organization settings webhooks">
<div role="main" aria-label="{{.Title}}" class="page-content organization settings webhooks">
{{template "org/header" .}}
<div class="ui container">
<div class="ui grid">

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content organization settings labels">
<div role="main" aria-label="{{.Title}}" class="page-content organization settings labels">
{{template "org/header" .}}
<div class="ui container">
<div class="ui grid">

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content organization settings options">
<div role="main" aria-label="{{.Title}}" class="page-content organization settings options">
{{template "org/header" .}}
<div class="ui container">
<div class="ui grid">

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content organization settings packages">
<div role="main" aria-label="{{.Title}}" class="page-content organization settings packages">
{{template "org/header" .}}
<div class="ui container">
<div class="ui grid">

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content organization settings packages">
<div role="main" aria-label="{{.Title}}" class="page-content organization settings packages">
{{template "org/header" .}}
<div class="ui container">
<div class="ui grid">

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content organization settings packages admin">
<div role="main" aria-label="{{.Title}}" class="page-content organization settings packages admin">
{{template "org/header" .}}
<div class="ui container">
<div class="ui grid">

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content organization settings webhooks">
<div role="main" aria-label="{{.Title}}" class="page-content organization settings webhooks">
{{template "org/header" .}}
<div class="ui container">
<div class="ui grid">

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content organization invite">
<div role="main" aria-label="{{.Title}}" class="page-content organization invite">
<div class="ui container">
{{template "base/alert" .}}
<div class="ui centered card">

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content organization teams">
<div role="main" aria-label="{{.Title}}" class="page-content organization teams">
{{template "org/header" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content organization new team">
<div role="main" aria-label="{{.Title}}" class="page-content organization new team">
{{template "org/header" .}}
<div class="ui container">
<div class="ui grid">

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content organization teams">
<div role="main" aria-label="{{.Title}}" class="page-content organization teams">
{{template "org/header" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content organization teams">
<div role="main" aria-label="{{.Title}}" class="page-content organization teams">
{{template "org/header" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -0,0 +1,30 @@
{{if eq .PackageDescriptor.Package.Type "conda"}}
<h4 class="ui top attached header">{{.locale.Tr "packages.installation"}}</h4>
<div class="ui attached segment">
<div class="ui form">
<div class="field">
<label>{{svg "octicon-code"}} {{.locale.Tr "packages.conda.registry" | Safe}}</label>
<div class="markup"><pre class="code-block"><code>channel_alias: {{AppUrl}}api/packages/{{.PackageDescriptor.Owner.Name}}/conda
channels:
&#32;&#32;- {{AppUrl}}api/packages/{{.PackageDescriptor.Owner.Name}}/conda
default_channels:
&#32;&#32;- {{AppUrl}}api/packages/{{.PackageDescriptor.Owner.Name}}/conda</code></pre></div>
</div>
<div class="field">
<label>{{svg "octicon-terminal"}} {{.locale.Tr "packages.conda.install"}}</label>
{{$channel := .PackageDescriptor.PackageProperties.GetByName "conda.channel"}}
<div class="markup"><pre class="code-block"><code>conda install{{if $channel}} -c {{$channel}}{{end}} {{.PackageDescriptor.PackageProperties.GetByName "conda.name"}}={{.PackageDescriptor.Version.Version}}</code></pre></div>
</div>
<div class="field">
<label>{{.locale.Tr "packages.conda.documentation" | Safe}}</label>
</div>
</div>
</div>
{{if or .PackageDescriptor.Metadata.Description .PackageDescriptor.Metadata.Summary}}
<h4 class="ui top attached header">{{.locale.Tr "packages.about"}}</h4>
<div class="ui attached segment">
{{if .PackageDescriptor.Metadata.Description}}{{.PackageDescriptor.Metadata.Description}}{{else}}{{.PackageDescriptor.Metadata.Summary}}{{end}}
</div>
{{end}}
{{end}}

View File

@ -0,0 +1,6 @@
{{if eq .PackageDescriptor.Package.Type "conda"}}
{{if .PackageDescriptor.Metadata.License}}<div class="item">{{svg "octicon-law" 16 "mr-3"}} {{.PackageDescriptor.Metadata.License}}</div>{{end}}
{{if .PackageDescriptor.Metadata.ProjectURL}}<div class="item">{{svg "octicon-link-external" 16 "mr-3"}} <a href="{{.PackageDescriptor.Metadata.ProjectURL}}" target="_blank" rel="noopener noreferrer me">{{.locale.Tr "packages.details.project_site"}}</a></div>{{end}}
{{if .PackageDescriptor.Metadata.RepositoryURL}}<div class="item">{{svg "octicon-link-external" 16 "mr-3"}} <a href="{{.PackageDescriptor.Metadata.RepositoryURL}}" target="_blank" rel="noopener noreferrer me">{{.locale.Tr "packages.conda.details.repository_site"}}</a></div>{{end}}
{{if .PackageDescriptor.Metadata.DocumentationURL}}<div class="item">{{svg "octicon-link-external" 16 "mr-3"}} <a href="{{.PackageDescriptor.Metadata.DocumentationURL}}" target="_blank" rel="noopener noreferrer me">{{.locale.Tr "packages.conda.details.documentation_site"}}</a></div>{{end}}
{{end}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content repository settings options">
<div role="main" aria-label="{{.Title}}" class="page-content repository settings options">
{{template "user/overview/header" .}}
<div class="ui container">
{{template "base/alert" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content repository view issue packages">
<div role="main" aria-label="{{.Title}}" class="page-content repository view issue packages">
{{template "user/overview/header" .}}
<div class="ui container">
<div>
@ -21,6 +21,7 @@
<div class="twelve wide column">
{{template "package/content/composer" .}}
{{template "package/content/conan" .}}
{{template "package/content/conda" .}}
{{template "package/content/container" .}}
{{template "package/content/generic" .}}
{{template "package/content/helm" .}}
@ -44,6 +45,7 @@
<div class="item">{{svg "octicon-download" 16 "mr-3"}} {{.PackageDescriptor.Version.DownloadCount}}</div>
{{template "package/metadata/composer" .}}
{{template "package/metadata/conan" .}}
{{template "package/metadata/conda" .}}
{{template "package/metadata/container" .}}
{{template "package/metadata/generic" .}}
{{template "package/metadata/helm" .}}

View File

@ -1,5 +1,5 @@
{{template "base/head" .}}
<div class="page-content install">
<div role="main" aria-label="{{.Title}}" class="page-content install">
<div class="ui container">
<div class="ui grid">
<div class="sixteen wide column content">

View File

@ -1,4 +1,4 @@
<div class="page-content repository projects">
<div role="main" aria-label="{{.Title}}" class="page-content repository projects">
<div class="ui container">
{{if .CanWriteProjects}}
<div class="navbar">

View File

@ -1,4 +1,4 @@
<div class="page-content repository projects edit-project new milestone">
<div role="main" aria-label="{{.Title}}" class="page-content repository projects edit-project new milestone">
<div class="ui container">
<div class="navbar">
{{if and .CanWriteProjects .PageIsEditProject}}

View File

@ -1,4 +1,4 @@
<div class="page-content repository projects view-project">
<div role="main" aria-label="{{.Title}}" class="page-content repository projects view-project">
<div class="ui container">
<div class="ui two column stackable grid">
<div class="column">

Some files were not shown because too many files have changed in this diff Show More