mirror of
				https://github.com/go-gitea/gitea.git
				synced 2025-11-03 08:02:36 +09:00 
			
		
		
		
	Compare commits
	
		
			37 Commits
		
	
	
		
			v1.19.0-de
			...
			v1.8.0
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					799f5e05c9 | ||
| 
						 | 
					497f37bffd | ||
| 
						 | 
					d7aa553f1b | ||
| 
						 | 
					ba12463175 | ||
| 
						 | 
					0acaa6bd00 | ||
| 
						 | 
					66a3353c31 | ||
| 
						 | 
					5236d8a936 | ||
| 
						 | 
					5876e37ed4 | ||
| 
						 | 
					3c21a0ee80 | ||
| 
						 | 
					f43783f003 | ||
| 
						 | 
					b9c5a3acc3 | ||
| 
						 | 
					c363ef5da0 | ||
| 
						 | 
					e8ca2da08f | ||
| 
						 | 
					f64b8eb009 | ||
| 
						 | 
					40f41dc694 | ||
| 
						 | 
					a63b9fbc70 | ||
| 
						 | 
					4b87aa367c | ||
| 
						 | 
					72f4cdf868 | ||
| 
						 | 
					5be1b7df3f | ||
| 
						 | 
					95e12be30f | ||
| 
						 | 
					245089b9c9 | ||
| 
						 | 
					2551660f49 | ||
| 
						 | 
					3b28de7d8e | ||
| 
						 | 
					3725eefb7f | ||
| 
						 | 
					73ce02400c | ||
| 
						 | 
					197cbd674d | ||
| 
						 | 
					4a0f7c1eb4 | ||
| 
						 | 
					e54f7a708c | ||
| 
						 | 
					63f6764dce | ||
| 
						 | 
					0bf7ed55be | ||
| 
						 | 
					93e8174e4e | ||
| 
						 | 
					c5ec66a8a3 | ||
| 
						 | 
					b6fb082b78 | ||
| 
						 | 
					3ce195115b | ||
| 
						 | 
					00619a04f7 | ||
| 
						 | 
					16815306ad | ||
| 
						 | 
					3934d9cd2f | 
							
								
								
									
										64
									
								
								.drone.yml
									
									
									
									
									
								
							
							
						
						
									
										64
									
								
								.drone.yml
									
									
									
									
									
								
							@@ -211,41 +211,41 @@ pipeline:
 | 
				
			|||||||
    when:
 | 
					    when:
 | 
				
			||||||
      event: [ push, tag, pull_request ]
 | 
					      event: [ push, tag, pull_request ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bench-sqlite:
 | 
					#  bench-sqlite:
 | 
				
			||||||
    image: golang:1.12
 | 
					#    image: golang:1.12
 | 
				
			||||||
    pull: true
 | 
					#    pull: true
 | 
				
			||||||
    group: bench
 | 
					#    group: bench
 | 
				
			||||||
    commands:
 | 
					#    commands:
 | 
				
			||||||
      - make bench-sqlite
 | 
					#      - make bench-sqlite
 | 
				
			||||||
    when:
 | 
					#    when:
 | 
				
			||||||
      event: [ tag ]
 | 
					#      event: [ tag ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bench-mysql:
 | 
					#  bench-mysql:
 | 
				
			||||||
    image: golang:1.12
 | 
					#    image: golang:1.12
 | 
				
			||||||
    pull: true
 | 
					#    pull: true
 | 
				
			||||||
    group: bench
 | 
					#    group: bench
 | 
				
			||||||
    commands:
 | 
					#    commands:
 | 
				
			||||||
      - make bench-mysql
 | 
					#      - make bench-mysql
 | 
				
			||||||
    when:
 | 
					#    when:
 | 
				
			||||||
      event: [ tag ]
 | 
					#      event: [ tag ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bench-mssql:
 | 
					#  bench-mssql:
 | 
				
			||||||
    image: golang:1.12
 | 
					#    image: golang:1.12
 | 
				
			||||||
    pull: true
 | 
					#    pull: true
 | 
				
			||||||
    group: bench
 | 
					#    group: bench
 | 
				
			||||||
    commands:
 | 
					#    commands:
 | 
				
			||||||
      - make bench-mssql
 | 
					#      - make bench-mssql
 | 
				
			||||||
    when:
 | 
					#    when:
 | 
				
			||||||
      event: [ tag ]
 | 
					#      event: [ tag ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  bench-pgsql:
 | 
					#  bench-pgsql:
 | 
				
			||||||
    image: golang:1.12
 | 
					#    image: golang:1.12
 | 
				
			||||||
    pull: true
 | 
					#    pull: true
 | 
				
			||||||
    group: bench
 | 
					#    group: bench
 | 
				
			||||||
    commands:
 | 
					#    commands:
 | 
				
			||||||
      - make bench-pgsql
 | 
					#      - make bench-pgsql
 | 
				
			||||||
    when:
 | 
					#    when:
 | 
				
			||||||
      event: [ tag ]
 | 
					#      event: [ tag ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  generate-coverage:
 | 
					  generate-coverage:
 | 
				
			||||||
    image: golang:1.12
 | 
					    image: golang:1.12
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										42
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								CHANGELOG.md
									
									
									
									
									
								
							@@ -4,7 +4,11 @@ This changelog goes through all the changes that have been made in each release
 | 
				
			|||||||
without substantial changes to our git log; to see the highlights of what has
 | 
					without substantial changes to our git log; to see the highlights of what has
 | 
				
			||||||
been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
					been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## [1.8.0-rc1](https://github.com/go-gitea/gitea/releases/tag/v1.8.0-rc1) - 2019-03-18
 | 
					## [1.8.0](https://github.com/go-gitea/gitea/releases/tag/v1.8.0) - 2019-04-20
 | 
				
			||||||
 | 
					* SECURITY
 | 
				
			||||||
 | 
					  * Prevent remote code execution vulnerability with mirror repo URL settings (#6593) (#6594)
 | 
				
			||||||
 | 
					  * Resolve 2FA bypass on API (#6676) (#6674)
 | 
				
			||||||
 | 
					  * Prevent the creation of empty sessions for non-logged in users (#6690) (#6677)
 | 
				
			||||||
* BREAKING
 | 
					* BREAKING
 | 
				
			||||||
  * Add "ghost" and "notifications" to list of reserved user names. (#6208)
 | 
					  * Add "ghost" and "notifications" to list of reserved user names. (#6208)
 | 
				
			||||||
  * Change sqlite DB path default to data directory (#6198)
 | 
					  * Change sqlite DB path default to data directory (#6198)
 | 
				
			||||||
@@ -84,7 +88,31 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
				
			|||||||
  * Allow markdown table to scroll (#4401)
 | 
					  * Allow markdown table to scroll (#4401)
 | 
				
			||||||
  * Automatically clear stopwatch on merging a PR (#4327)
 | 
					  * Automatically clear stopwatch on merging a PR (#4327)
 | 
				
			||||||
  * Add the Owner Name to differentiate when merging (#3807)
 | 
					  * Add the Owner Name to differentiate when merging (#3807)
 | 
				
			||||||
 | 
					  * Add title attributes to all items in the repo list viewer (#6258) (#6650)
 | 
				
			||||||
* BUGFIXES
 | 
					* BUGFIXES
 | 
				
			||||||
 | 
					  * Fix dropdown icon padding (#6651) (#6654)
 | 
				
			||||||
 | 
					  * Fix wrong GPG expire date (#6643) (#6644)
 | 
				
			||||||
 | 
					  * Fix forking an empty repository (#6637) (#6653)
 | 
				
			||||||
 | 
					  * Remove call to EscapePound .Link as it is already escaped (#6656) (#6666)
 | 
				
			||||||
 | 
					  * Properly escape on the redirect from the web editor (#6657) (#6667)
 | 
				
			||||||
 | 
					  * Allow resend of confirmation email when logged in (#6482) (#6486)
 | 
				
			||||||
 | 
					  * Fix mail notification when close/reopen issue (#6581) (#6588)
 | 
				
			||||||
 | 
					  * Change API commit summary to full message (#6591) (#6592)
 | 
				
			||||||
 | 
					  * Add option to disable refresh token invalidation (#6584) (#6587)
 | 
				
			||||||
 | 
					  * Fix bug user search API pagesize didn't obey ExplorePagingNum (#6579) (#6586)
 | 
				
			||||||
 | 
					  * Fix new repo alignment (#6583) (#6585)
 | 
				
			||||||
 | 
					  * Prevent server 500 on compare branches with no common history (#6555) (#6558)
 | 
				
			||||||
 | 
					  * Properly escape release attachment URL (#6512) (#6523)
 | 
				
			||||||
 | 
					  * Hacky fix for alignment of the create-organization dialog (#6455) (#6462)
 | 
				
			||||||
 | 
					  * Disable benchmarking during tag events on DroneIO (#6365) (#6366)
 | 
				
			||||||
 | 
					  * Make sure units of a team are returned (#6379) (#6381)
 | 
				
			||||||
 | 
					  * Don't Unescape redirect_to cookie value (#6399) (#6401)
 | 
				
			||||||
 | 
					  * Fix dump table name error and add some test for dump database (#6394) (#6402)
 | 
				
			||||||
 | 
					  * Fix migration v82 to ignore unsynced tags between database and git data; Add missing is_archived column on repository table (#6387) (#6403)
 | 
				
			||||||
 | 
					  * Display correct error for invalid mirror interval (#6414) (#6429)
 | 
				
			||||||
 | 
					  * Clean up ref name rules (#6437) (#6439)
 | 
				
			||||||
 | 
					  * Fix Hook & HookList in Swagger (#6432) (#6440)
 | 
				
			||||||
 | 
					  * Change order that PostProcess Processors are run (#6445) (#6447)
 | 
				
			||||||
  * Clean up various use of escape/unescape functions for URL generation (#6334)
 | 
					  * Clean up various use of escape/unescape functions for URL generation (#6334)
 | 
				
			||||||
  * Return 409 when creating repo if it already exists. (#6330)
 | 
					  * Return 409 when creating repo if it already exists. (#6330)
 | 
				
			||||||
  * Add same changes from issues page to milestone->issues page (#6328)
 | 
					  * Add same changes from issues page to milestone->issues page (#6328)
 | 
				
			||||||
@@ -210,6 +238,18 @@ been added to each release, please refer to the [blog](https://blog.gitea.io).
 | 
				
			|||||||
  * Add missing GET teams endpoints (#5382)
 | 
					  * Add missing GET teams endpoints (#5382)
 | 
				
			||||||
  * Migrate database if app.ini found (#5290)
 | 
					  * Migrate database if app.ini found (#5290)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## [1.7.6](https://github.com/go-gitea/gitea/releases/tag/v1.7.6) - 2019-04-12
 | 
				
			||||||
 | 
					* SECURITY
 | 
				
			||||||
 | 
					  * Prevent remote code execution vulnerability with mirror repo URL settings (#6593) (#6595)
 | 
				
			||||||
 | 
					* BUGFIXES
 | 
				
			||||||
 | 
					  * Allow resend of confirmation email when logged in (#6482) (#6487)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## [1.7.5](https://github.com/go-gitea/gitea/releases/tag/v1.7.5) - 2019-03-27
 | 
				
			||||||
 | 
					* BUGFIXES
 | 
				
			||||||
 | 
					  * Fix unitTypeCode not being used in accessLevelUnit (#6419) (#6423)
 | 
				
			||||||
 | 
					  * Fix bug where manifest.json was being requested without cookies and continuously creating new sessions (#6372) (#6383) 
 | 
				
			||||||
 | 
					  * Fix ParsePatch function to work with quoted diff --git strings (#6323) (#6332)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## [1.7.4](https://github.com/go-gitea/gitea/releases/tag/v1.7.4) - 2019-03-12
 | 
					## [1.7.4](https://github.com/go-gitea/gitea/releases/tag/v1.7.4) - 2019-03-12
 | 
				
			||||||
* SECURITY
 | 
					* SECURITY
 | 
				
			||||||
  * Fix potential XSS vulnerability in repository description. (#6306) (#6308)
 | 
					  * Fix potential XSS vulnerability in repository description. (#6306) (#6308)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								Gopkg.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										4
									
								
								Gopkg.lock
									
									
									
										generated
									
									
									
								
							@@ -3,11 +3,11 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
[[projects]]
 | 
					[[projects]]
 | 
				
			||||||
  branch = "master"
 | 
					  branch = "master"
 | 
				
			||||||
  digest = "1:e1fa64238b0a2dbf1edf98c4af8d1b8cb65179e286d7f28006b50fa9f508ee9d"
 | 
					  digest = "1:c298eea5ff7f6ab40cda6fe75d2224e2dd271941abe2f66276063b39e43e5687"
 | 
				
			||||||
  name = "code.gitea.io/git"
 | 
					  name = "code.gitea.io/git"
 | 
				
			||||||
  packages = ["."]
 | 
					  packages = ["."]
 | 
				
			||||||
  pruneopts = "NUT"
 | 
					  pruneopts = "NUT"
 | 
				
			||||||
  revision = "74d7c14dd4a3ed9c5def0dc3c1aeede399ddc5c5"
 | 
					  revision = "63b74d438b29bb272fa9b4010abe3f50a832e7ef"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[projects]]
 | 
					[[projects]]
 | 
				
			||||||
  branch = "master"
 | 
					  branch = "master"
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										15
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								Makefile
									
									
									
									
									
								
							@@ -3,6 +3,7 @@ IMPORT := code.gitea.io/gitea
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
GO ?= go
 | 
					GO ?= go
 | 
				
			||||||
SED_INPLACE := sed -i
 | 
					SED_INPLACE := sed -i
 | 
				
			||||||
 | 
					SHASUM ?= shasum -a 256
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export PATH := $($(GO) env GOPATH)/bin:$(PATH)
 | 
					export PATH := $($(GO) env GOPATH)/bin:$(PATH)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -148,7 +149,7 @@ misspell-check:
 | 
				
			|||||||
	@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
 | 
						@hash misspell > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
 | 
				
			||||||
		$(GO) get -u github.com/client9/misspell/cmd/misspell; \
 | 
							$(GO) get -u github.com/client9/misspell/cmd/misspell; \
 | 
				
			||||||
	fi
 | 
						fi
 | 
				
			||||||
	misspell -error -i unknwon $(GOFILES)
 | 
						misspell -error -i unknwon,destory $(GOFILES)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.PHONY: misspell
 | 
					.PHONY: misspell
 | 
				
			||||||
misspell:
 | 
					misspell:
 | 
				
			||||||
@@ -327,7 +328,7 @@ release-windows:
 | 
				
			|||||||
	fi
 | 
						fi
 | 
				
			||||||
	xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
 | 
						xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'windows/*' -out gitea-$(VERSION) .
 | 
				
			||||||
ifeq ($(CI),drone)
 | 
					ifeq ($(CI),drone)
 | 
				
			||||||
	mv /build/* $(DIST)/binaries
 | 
						cp /build/* $(DIST)/binaries
 | 
				
			||||||
endif
 | 
					endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.PHONY: release-linux
 | 
					.PHONY: release-linux
 | 
				
			||||||
@@ -337,7 +338,7 @@ release-linux:
 | 
				
			|||||||
	fi
 | 
						fi
 | 
				
			||||||
	xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'linux/*' -out gitea-$(VERSION) .
 | 
						xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '-linkmode external -extldflags "-static" $(LDFLAGS)' -targets 'linux/*' -out gitea-$(VERSION) .
 | 
				
			||||||
ifeq ($(CI),drone)
 | 
					ifeq ($(CI),drone)
 | 
				
			||||||
	mv /build/* $(DIST)/binaries
 | 
						cp /build/* $(DIST)/binaries
 | 
				
			||||||
endif
 | 
					endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.PHONY: release-darwin
 | 
					.PHONY: release-darwin
 | 
				
			||||||
@@ -347,23 +348,23 @@ release-darwin:
 | 
				
			|||||||
	fi
 | 
						fi
 | 
				
			||||||
	xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/*' -out gitea-$(VERSION) .
 | 
						xgo -dest $(DIST)/binaries -tags 'netgo $(TAGS)' -ldflags '$(LDFLAGS)' -targets 'darwin/*' -out gitea-$(VERSION) .
 | 
				
			||||||
ifeq ($(CI),drone)
 | 
					ifeq ($(CI),drone)
 | 
				
			||||||
	mv /build/* $(DIST)/binaries
 | 
						cp /build/* $(DIST)/binaries
 | 
				
			||||||
endif
 | 
					endif
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.PHONY: release-copy
 | 
					.PHONY: release-copy
 | 
				
			||||||
release-copy:
 | 
					release-copy:
 | 
				
			||||||
	$(foreach file,$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*),cp $(file) $(DIST)/release/$(notdir $(file));)
 | 
						cd $(DIST); for file in `find /build -type f -name "*"`; do cp $${file} ./release/; done;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.PHONY: release-check
 | 
					.PHONY: release-check
 | 
				
			||||||
release-check:
 | 
					release-check:
 | 
				
			||||||
	cd $(DIST)/release; $(foreach file,$(wildcard $(DIST)/release/$(EXECUTABLE)-*),sha256sum $(notdir $(file)) > $(notdir $(file)).sha256;)
 | 
						cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "checksumming $${file}" && $(SHASUM) `echo $${file} | sed 's/^..//'` > $${file}.sha256; done;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.PHONY: release-compress
 | 
					.PHONY: release-compress
 | 
				
			||||||
release-compress:
 | 
					release-compress:
 | 
				
			||||||
	@hash gxz > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
 | 
						@hash gxz > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
 | 
				
			||||||
		$(GO) get -u github.com/ulikunitz/xz/cmd/gxz; \
 | 
							$(GO) get -u github.com/ulikunitz/xz/cmd/gxz; \
 | 
				
			||||||
	fi
 | 
						fi
 | 
				
			||||||
	cd $(DIST)/release; $(foreach file,$(wildcard $(DIST)/binaries/$(EXECUTABLE)-*),gxz -k -9 $(notdir $(file));)
 | 
						cd $(DIST)/release/; for file in `find . -type f -name "*"`; do echo "compressing $${file}" && gxz -k -9 $${file}; done;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.PHONY: javascripts
 | 
					.PHONY: javascripts
 | 
				
			||||||
javascripts: public/js/index.js
 | 
					javascripts: public/js/index.js
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -661,6 +661,8 @@ ENABLED = true
 | 
				
			|||||||
ACCESS_TOKEN_EXPIRATION_TIME=3600
 | 
					ACCESS_TOKEN_EXPIRATION_TIME=3600
 | 
				
			||||||
; Lifetime of an OAuth2 access token in hours
 | 
					; Lifetime of an OAuth2 access token in hours
 | 
				
			||||||
REFRESH_TOKEN_EXPIRATION_TIME=730
 | 
					REFRESH_TOKEN_EXPIRATION_TIME=730
 | 
				
			||||||
 | 
					; Check if refresh token got already used
 | 
				
			||||||
 | 
					INVALIDATE_REFRESH_TOKENS=false
 | 
				
			||||||
; OAuth2 authentication secret for access and refresh tokens, change this a unique string.
 | 
					; OAuth2 authentication secret for access and refresh tokens, change this a unique string.
 | 
				
			||||||
JWT_SECRET=Bk0yK7Y9g_p56v86KaHqjSbxvNvu3SbKoOdOt2ZcXvU
 | 
					JWT_SECRET=Bk0yK7Y9g_p56v86KaHqjSbxvNvu3SbKoOdOt2ZcXvU
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -350,6 +350,7 @@ Values containing `#` or `;` must be quoted using `` ` `` or `"""`.
 | 
				
			|||||||
- `ENABLED`: **true**: Enables OAuth2 provider.
 | 
					- `ENABLED`: **true**: Enables OAuth2 provider.
 | 
				
			||||||
- `ACCESS_TOKEN_EXPIRATION_TIME`: **3600**: Lifetime of an OAuth2 access token in seconds
 | 
					- `ACCESS_TOKEN_EXPIRATION_TIME`: **3600**: Lifetime of an OAuth2 access token in seconds
 | 
				
			||||||
- `REFRESH_TOKEN_EXPIRATION_TIME`: **730**: Lifetime of an OAuth2 access token in hours
 | 
					- `REFRESH_TOKEN_EXPIRATION_TIME`: **730**: Lifetime of an OAuth2 access token in hours
 | 
				
			||||||
 | 
					- `INVALIDATE_REFRESH_TOKEN`: **false**: Check if refresh token got already used
 | 
				
			||||||
- `JWT_SECRET`: **\<empty\>**: OAuth2 authentication secret for access and refresh tokens, change this a unique string.
 | 
					- `JWT_SECRET`: **\<empty\>**: OAuth2 authentication secret for access and refresh tokens, change this a unique string.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## i18n (`i18n`)
 | 
					## i18n (`i18n`)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -106,3 +106,26 @@ func TestAPISudoUserForbidden(t *testing.T) {
 | 
				
			|||||||
	req := NewRequest(t, "GET", urlStr)
 | 
						req := NewRequest(t, "GET", urlStr)
 | 
				
			||||||
	session.MakeRequest(t, req, http.StatusForbidden)
 | 
						session.MakeRequest(t, req, http.StatusForbidden)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestAPIListUsers(t *testing.T) {
 | 
				
			||||||
 | 
						prepareTestEnv(t)
 | 
				
			||||||
 | 
						adminUsername := "user1"
 | 
				
			||||||
 | 
						session := loginUser(t, adminUsername)
 | 
				
			||||||
 | 
						token := getTokenForLoggedInUser(t, session)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						urlStr := fmt.Sprintf("/api/v1/admin/users?token=%s", token)
 | 
				
			||||||
 | 
						req := NewRequest(t, "GET", urlStr)
 | 
				
			||||||
 | 
						resp := session.MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
 | 
						var users []api.User
 | 
				
			||||||
 | 
						DecodeJSON(t, resp, &users)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						found := false
 | 
				
			||||||
 | 
						for _, user := range users {
 | 
				
			||||||
 | 
							if user.UserName == adminUsername {
 | 
				
			||||||
 | 
								found = true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						assert.True(t, found)
 | 
				
			||||||
 | 
						numberOfUsers := models.GetCount(t, &models.User{}, "type = 0")
 | 
				
			||||||
 | 
						assert.Equal(t, numberOfUsers, len(users))
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										115
									
								
								integrations/create_no_session_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										115
									
								
								integrations/create_no_session_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,115 @@
 | 
				
			|||||||
 | 
					// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package integrations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"net/http/httptest"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/routers/routes"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-macaron/session"
 | 
				
			||||||
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getSessionID(t *testing.T, resp *httptest.ResponseRecorder) string {
 | 
				
			||||||
 | 
						cookies := resp.Result().Cookies()
 | 
				
			||||||
 | 
						found := false
 | 
				
			||||||
 | 
						sessionID := ""
 | 
				
			||||||
 | 
						for _, cookie := range cookies {
 | 
				
			||||||
 | 
							if cookie.Name == setting.SessionConfig.CookieName {
 | 
				
			||||||
 | 
								sessionID = cookie.Value
 | 
				
			||||||
 | 
								found = true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						assert.True(t, found)
 | 
				
			||||||
 | 
						assert.NotEmpty(t, sessionID)
 | 
				
			||||||
 | 
						return sessionID
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func sessionFile(tmpDir, sessionID string) string {
 | 
				
			||||||
 | 
						return filepath.Join(tmpDir, sessionID[0:1], sessionID[1:2], sessionID)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func sessionFileExist(t *testing.T, tmpDir, sessionID string) bool {
 | 
				
			||||||
 | 
						sessionFile := sessionFile(tmpDir, sessionID)
 | 
				
			||||||
 | 
						_, err := os.Lstat(sessionFile)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if os.IsNotExist(err) {
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							assert.NoError(t, err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestSessionFileCreation(t *testing.T) {
 | 
				
			||||||
 | 
						prepareTestEnv(t)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						oldSessionConfig := setting.SessionConfig.ProviderConfig
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							setting.SessionConfig.ProviderConfig = oldSessionConfig
 | 
				
			||||||
 | 
							mac = routes.NewMacaron()
 | 
				
			||||||
 | 
							routes.RegisterRoutes(mac)
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						var config session.Options
 | 
				
			||||||
 | 
						err := json.Unmarshal([]byte(oldSessionConfig), &config)
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						config.Provider = "file"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Now create a temporaryDirectory
 | 
				
			||||||
 | 
						tmpDir, err := ioutil.TempDir("", "sessions")
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
						defer func() {
 | 
				
			||||||
 | 
							if _, err := os.Stat(tmpDir); !os.IsNotExist(err) {
 | 
				
			||||||
 | 
								_ = os.RemoveAll(tmpDir)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}()
 | 
				
			||||||
 | 
						config.ProviderConfig = tmpDir
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						newConfigBytes, err := json.Marshal(config)
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						setting.SessionConfig.ProviderConfig = string(newConfigBytes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						mac = routes.NewMacaron()
 | 
				
			||||||
 | 
						routes.RegisterRoutes(mac)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						t.Run("NoSessionOnViewIssue", func(t *testing.T) {
 | 
				
			||||||
 | 
							req := NewRequest(t, "GET", "/user2/repo1/issues/1")
 | 
				
			||||||
 | 
							resp := MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
 | 
							sessionID := getSessionID(t, resp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// We're not logged in so there should be no session
 | 
				
			||||||
 | 
							assert.False(t, sessionFileExist(t, tmpDir, sessionID))
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						t.Run("CreateSessionOnLogin", func(t *testing.T) {
 | 
				
			||||||
 | 
							req := NewRequest(t, "GET", "/user/login")
 | 
				
			||||||
 | 
							resp := MakeRequest(t, req, http.StatusOK)
 | 
				
			||||||
 | 
							sessionID := getSessionID(t, resp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// We're not logged in so there should be no session
 | 
				
			||||||
 | 
							assert.False(t, sessionFileExist(t, tmpDir, sessionID))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							doc := NewHTMLParser(t, resp.Body)
 | 
				
			||||||
 | 
							req = NewRequestWithValues(t, "POST", "/user/login", map[string]string{
 | 
				
			||||||
 | 
								"_csrf":     doc.GetCSRF(),
 | 
				
			||||||
 | 
								"user_name": "user2",
 | 
				
			||||||
 | 
								"password":  userPassword,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							resp = MakeRequest(t, req, http.StatusFound)
 | 
				
			||||||
 | 
							sessionID = getSessionID(t, resp)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							assert.FileExists(t, sessionFile(tmpDir, sessionID))
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -8,6 +8,8 @@ import (
 | 
				
			|||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -73,7 +75,30 @@ func TestAccessTokenExchange(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func TestAccessTokenExchangeWithoutPKCE(t *testing.T) {
 | 
					func TestAccessTokenExchangeWithoutPKCE(t *testing.T) {
 | 
				
			||||||
	prepareTestEnv(t)
 | 
						prepareTestEnv(t)
 | 
				
			||||||
	req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
 | 
						req := NewRequestWithJSON(t, "POST", "/login/oauth/access_token", map[string]string{
 | 
				
			||||||
 | 
							"grant_type":    "authorization_code",
 | 
				
			||||||
 | 
							"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
 | 
				
			||||||
 | 
							"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
 | 
				
			||||||
 | 
							"redirect_uri":  "a",
 | 
				
			||||||
 | 
							"code":          "authcode",
 | 
				
			||||||
 | 
							"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						resp := MakeRequest(t, req, 200)
 | 
				
			||||||
 | 
						type response struct {
 | 
				
			||||||
 | 
							AccessToken  string `json:"access_token"`
 | 
				
			||||||
 | 
							TokenType    string `json:"token_type"`
 | 
				
			||||||
 | 
							ExpiresIn    int64  `json:"expires_in"`
 | 
				
			||||||
 | 
							RefreshToken string `json:"refresh_token"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						parsed := new(response)
 | 
				
			||||||
 | 
						assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed))
 | 
				
			||||||
 | 
						assert.True(t, len(parsed.AccessToken) > 10)
 | 
				
			||||||
 | 
						assert.True(t, len(parsed.RefreshToken) > 10)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestAccessTokenExchangeJSON(t *testing.T) {
 | 
				
			||||||
 | 
						prepareTestEnv(t)
 | 
				
			||||||
 | 
						req := NewRequestWithJSON(t, "POST", "/login/oauth/access_token", map[string]string{
 | 
				
			||||||
		"grant_type":    "authorization_code",
 | 
							"grant_type":    "authorization_code",
 | 
				
			||||||
		"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
 | 
							"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
 | 
				
			||||||
		"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
 | 
							"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
 | 
				
			||||||
@@ -177,3 +202,42 @@ func TestAccessTokenExchangeWithBasicAuth(t *testing.T) {
 | 
				
			|||||||
	})
 | 
						})
 | 
				
			||||||
	resp = MakeRequest(t, req, 400)
 | 
						resp = MakeRequest(t, req, 400)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestRefreshTokenInvalidation(t *testing.T) {
 | 
				
			||||||
 | 
						prepareTestEnv(t)
 | 
				
			||||||
 | 
						req := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
 | 
				
			||||||
 | 
							"grant_type":    "authorization_code",
 | 
				
			||||||
 | 
							"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
 | 
				
			||||||
 | 
							"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
 | 
				
			||||||
 | 
							"redirect_uri":  "a",
 | 
				
			||||||
 | 
							"code":          "authcode",
 | 
				
			||||||
 | 
							"code_verifier": "N1Zo9-8Rfwhkt68r1r29ty8YwIraXR8eh_1Qwxg7yQXsonBt", // test PKCE additionally
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						resp := MakeRequest(t, req, 200)
 | 
				
			||||||
 | 
						type response struct {
 | 
				
			||||||
 | 
							AccessToken  string `json:"access_token"`
 | 
				
			||||||
 | 
							TokenType    string `json:"token_type"`
 | 
				
			||||||
 | 
							ExpiresIn    int64  `json:"expires_in"`
 | 
				
			||||||
 | 
							RefreshToken string `json:"refresh_token"`
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						parsed := new(response)
 | 
				
			||||||
 | 
						assert.NoError(t, json.Unmarshal(resp.Body.Bytes(), parsed))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// test without invalidation
 | 
				
			||||||
 | 
						setting.OAuth2.InvalidateRefreshTokens = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						refreshReq := NewRequestWithValues(t, "POST", "/login/oauth/access_token", map[string]string{
 | 
				
			||||||
 | 
							"grant_type":    "refresh_token",
 | 
				
			||||||
 | 
							"client_id":     "da7da3ba-9a13-4167-856f-3899de0b0138",
 | 
				
			||||||
 | 
							"client_secret": "4MK8Na6R55smdCY0WuCCumZ6hjRPnGY5saWVRHHjJiA=",
 | 
				
			||||||
 | 
							"redirect_uri":  "a",
 | 
				
			||||||
 | 
							"refresh_token": parsed.RefreshToken,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						MakeRequest(t, refreshReq, 200)
 | 
				
			||||||
 | 
						MakeRequest(t, refreshReq, 200)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// test with invalidation
 | 
				
			||||||
 | 
						setting.OAuth2.InvalidateRefreshTokens = true
 | 
				
			||||||
 | 
						MakeRequest(t, refreshReq, 200)
 | 
				
			||||||
 | 
						MakeRequest(t, refreshReq, 400)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -58,7 +58,7 @@ func TestCreateBranch(t *testing.T) {
 | 
				
			|||||||
			OldRefSubURL:   "branch/master",
 | 
								OldRefSubURL:   "branch/master",
 | 
				
			||||||
			NewBranch:      "feature=test1",
 | 
								NewBranch:      "feature=test1",
 | 
				
			||||||
			ExpectedStatus: http.StatusFound,
 | 
								ExpectedStatus: http.StatusFound,
 | 
				
			||||||
			FlashMessage:   i18n.Tr("en", "form.NewBranchName") + i18n.Tr("en", "form.git_ref_name_error"),
 | 
								FlashMessage:   i18n.Tr("en", "repo.branch.create_success", "feature=test1"),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			OldRefSubURL:   "branch/master",
 | 
								OldRefSubURL:   "branch/master",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -164,10 +164,9 @@ func parseSubGPGKey(ownerID int64, primaryID string, pubkey *packet.PublicKey, e
 | 
				
			|||||||
	}, nil
 | 
						}, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
//parseGPGKey parse a PrimaryKey entity (primary key + subs keys + self-signature)
 | 
					//getExpiryTime extract the expire time of primary key based on sig
 | 
				
			||||||
func parseGPGKey(ownerID int64, e *openpgp.Entity) (*GPGKey, error) {
 | 
					func getExpiryTime(e *openpgp.Entity) time.Time {
 | 
				
			||||||
	pubkey := e.PrimaryKey
 | 
						expiry := time.Time{}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	//Extract self-sign for expire date based on : https://github.com/golang/crypto/blob/master/openpgp/keys.go#L165
 | 
						//Extract self-sign for expire date based on : https://github.com/golang/crypto/blob/master/openpgp/keys.go#L165
 | 
				
			||||||
	var selfSig *packet.Signature
 | 
						var selfSig *packet.Signature
 | 
				
			||||||
	for _, ident := range e.Identities {
 | 
						for _, ident := range e.Identities {
 | 
				
			||||||
@@ -178,10 +177,16 @@ func parseGPGKey(ownerID int64, e *openpgp.Entity) (*GPGKey, error) {
 | 
				
			|||||||
			break
 | 
								break
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	expiry := time.Time{}
 | 
					 | 
				
			||||||
	if selfSig.KeyLifetimeSecs != nil {
 | 
						if selfSig.KeyLifetimeSecs != nil {
 | 
				
			||||||
		expiry = selfSig.CreationTime.Add(time.Duration(*selfSig.KeyLifetimeSecs) * time.Second)
 | 
							expiry = e.PrimaryKey.CreationTime.Add(time.Duration(*selfSig.KeyLifetimeSecs) * time.Second)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						return expiry
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//parseGPGKey parse a PrimaryKey entity (primary key + subs keys + self-signature)
 | 
				
			||||||
 | 
					func parseGPGKey(ownerID int64, e *openpgp.Entity) (*GPGKey, error) {
 | 
				
			||||||
 | 
						pubkey := e.PrimaryKey
 | 
				
			||||||
 | 
						expiry := getExpiryTime(e)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	//Parse Subkeys
 | 
						//Parse Subkeys
 | 
				
			||||||
	subkeys := make([]*GPGKey, len(e.Subkeys))
 | 
						subkeys := make([]*GPGKey, len(e.Subkeys))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,7 @@ package models
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/modules/util"
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -225,3 +226,153 @@ Q0KHb+QcycSgbDx0ZAvdIacuKvBBcbxrsmFUI4LR+oIup0G9gUc0roPvr014jYQL
 | 
				
			|||||||
		assert.Equal(t, "user1@example.com", key.Emails[0].Email)
 | 
							assert.Equal(t, "user1@example.com", key.Emails[0].Email)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestCheckGParseGPGExpire(t *testing.T) {
 | 
				
			||||||
 | 
						testIssue6599 := `-----BEGIN PGP PUBLIC KEY BLOCK-----
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					mQINBFlFJRsBEAClNcRT5El+EaTtQEYs/eNAhr/bqiyt6fPMtabDq2x6a8wFWMX0
 | 
				
			||||||
 | 
					yhRh4vZuLzhi95DU/pmhZARt0W15eiN0AhWdOKxry1KtZNiZBzMm1f0qZJMuBG8g
 | 
				
			||||||
 | 
					YJ7aRkCqdWRxy1Q+U/yhr6z7ucD8/yn7u5wke/jsPdF/L8I/HKNHoawI1FcMC9v+
 | 
				
			||||||
 | 
					QoG3pIX8NVGdzaUYygFG1Gxofc3pb3i4pcpOUxpOP12t6PfwTCoAWZtRLgxTdwWn
 | 
				
			||||||
 | 
					DGvY6SCIIIxn4AC6u3+tHz9HDXx+4eiB7VxMsiIsEuHW9DVBzen9jFNNjRnNaFkL
 | 
				
			||||||
 | 
					pTAFOyGsSzGRGhuJpb7j7hByoWkaItqaw+clnzVrDqhfbxS1B8dmgMANh9pzNsv7
 | 
				
			||||||
 | 
					J/OnNdGsbgDX5RytSKMaXclK2ZGH6Txatgezo167z6EdthNR1daj1QfqWADiqKbR
 | 
				
			||||||
 | 
					UXp7Xz9b+/CBedUNEXPbIExva9mPsFJo2IEntRGtdhhjuO4a6HLG7k1i0o0dHxqb
 | 
				
			||||||
 | 
					a9HrOW7fO902L7JHIgnjpDWDGLGGnVGcGWdEEZggfpnvjxADeTgyMb2XkALTQ0GG
 | 
				
			||||||
 | 
					yRywByxG8/zjXeEkqUng/mxNbBCcHcuIRVsqYwGQLiLubYxnRudqtNst8Tdu+0+q
 | 
				
			||||||
 | 
					AL0bb8ueQC1M3WHsMUxvTjknFJdJzRicNyLf6AdfRv6yy6Ra+t4SFoSbsQARAQAB
 | 
				
			||||||
 | 
					tB90YXN0eXRlYSA8dGFzdHl0ZWFAdGFzdHl0ZWEuZGU+iQJXBBMBCABBAhsDBQsJ
 | 
				
			||||||
 | 
					CAcCBhUICQoLAgQWAgMBAh4BAheAAhkBFiEE1bTEO0ioefY1KTbmWTRuDqNcZ+UF
 | 
				
			||||||
 | 
					Alyo2K0FCQVE5xIACgkQWTRuDqNcZ+UTFA/+IygU02oz19tRVNgVmKyXv1GhnkaY
 | 
				
			||||||
 | 
					O/oGxp7cRGJ0gf0bjhbJpFf4+6OHaS0ei47Qp8XTuStfWry6V6rXLSV/ZOOhFaCq
 | 
				
			||||||
 | 
					VpFvoG2JcPZbSTB+CR/lL5fWwx3w5PAOUwipGRFs7mYLgy8U/E3U7u+ioP4ZqCXS
 | 
				
			||||||
 | 
					heclyXAGNlrjUwvwOWRLxvcEQr4ztQR0Lk2tv1QYYDzbaXUSdnsM1YK9YpYP7BE2
 | 
				
			||||||
 | 
					luKtwwXaubdwcXPs96FEmGLGfsWC/dWnAxkYXPo9q7O6c5GKbGiP3xFhBaBCzzm0
 | 
				
			||||||
 | 
					PAqAJ+NyIWL63yI1aNNz4xC1marU7UPLzBnv5fG1WdscYqAbj8XbZ96mPPM80y0A
 | 
				
			||||||
 | 
					j5/7YecRXce4yedxRHhi3bD8MEzDMHWfkQPpWCZj/KwjDFiZwSMgpQUqeAllDKQx
 | 
				
			||||||
 | 
					Ld0CLkLuUe20b+/5h6dGtGpoACkoOPxMl6zi9uihztvR5iYdkwnmcxKmnEtz+WV4
 | 
				
			||||||
 | 
					1efhS3QRZro3QAHjhCqU1Xjl0hnwSCgP5nUhTq6dJqgeZ7c5D4Uhg55MXwQ68Oe4
 | 
				
			||||||
 | 
					NrQfhdO8IOSVPDPDEeQ2kuP7/HEZsjKZBMKhKoUcdXM6y9T2tYw3wv5JDuDxT2Q1
 | 
				
			||||||
 | 
					3IuFVr1uFm/spVyFCpPpPSQM1wfdtoPLRjiJ/KVh777AWUlywP2b7cWyKShYJb4P
 | 
				
			||||||
 | 
					QzTQ/udx94916cSJAlQEEwEIAD4WIQTVtMQ7SKh59jUpNuZZNG4Oo1xn5QUCWUUl
 | 
				
			||||||
 | 
					GwIbAwUJA8ORBQULCQgHAgYVCAkKCwIEFgIDAQIeAQIXgAAKCRBZNG4Oo1xn5Uoa
 | 
				
			||||||
 | 
					D/9tdmXECDZS1th0xmdNIsecxhI9dBGJyaJwfhH7UVkL+e86EsmTSzyJhBAepDDe
 | 
				
			||||||
 | 
					4wTEaW/NnjVX+ulO7rKFN4/qvSCOaeIdP0MEn7zfZVVKG8gMW4mb/piLvUnsZvsM
 | 
				
			||||||
 | 
					eWfv9AL/b3H1MRkl9S6XsE0ove72pmbBSZEhh2rNHqf+tIGr/RTtn80efTv3w+75
 | 
				
			||||||
 | 
					0UJtaFPsAKoAzNRy+ouhf9IHy9pEMJRA/hZ0Ho04QCDAC65mWz7iwI7v9VRDVfng
 | 
				
			||||||
 | 
					UjJPJahoM4vTpB30vJiFYT2oFTgdxGckfEUezsk8Rx/o6x4u6igKypPbeqM/7SMw
 | 
				
			||||||
 | 
					H61sCWR7nHJhCK55WeEIbzHEhwCZTf1pgvHj5oGUOjzksp2DmFV3ma3WCh8JyqyA
 | 
				
			||||||
 | 
					zw2OvOXBlayIaGIoyD5tSHS40rTi9JmOUfhg6WPN3MIrvsSVEV7JNdiZs/Tb07eQ
 | 
				
			||||||
 | 
					l71O7wv/LXZZCYP5NLV0PJbN2pHMf8cysWulfHN/mNgpEiLJpPBYVVyVbzWLg54X
 | 
				
			||||||
 | 
					FcNQMrT70kRF4M2GBRahXchkWi6+1pd3jPtvCFfcNiYBnHcrKu2R/UdSpFYdclDi
 | 
				
			||||||
 | 
					y6u7xMxXt0AVeLLtlXq7+ChOANMH5aPdUjCXeQDNJawLx41KL9fETsjScodmmpKi
 | 
				
			||||||
 | 
					SNhkC03FNfbkPJzZthoTxCfUBQeHYWgDpN3Gjb/OdSWC34kCVwQTAQgAQQIbAwUJ
 | 
				
			||||||
 | 
					A8ORBQULCQgHAgYVCAkKCwIEFgIDAQIeAQIXgBYhBNW0xDtIqHn2NSk25lk0bg6j
 | 
				
			||||||
 | 
					XGflBQJcqNWQAhkBAAoJEFk0bg6jXGfldcEP/iz4UbJPd/kr8D008ky7vI7hnYs8
 | 
				
			||||||
 | 
					VQIxL6ljQJ75XmVx/Lz1MVo4Vdsu6+qEta5gvqbGwjuEugaHcFVbHCZEBKI0QHSQ
 | 
				
			||||||
 | 
					UNHfXT8eZP/BwwFWawUokLTbF//Dg5xd5ejo/TeltNleyq1r0AoxcoMv1srrY4yK
 | 
				
			||||||
 | 
					GvWE5V8SVSi/E71y4VarS58ZH3NZ6sW5slnYvgAHTVgOjkVvMYk5JmrWsFsycYf8
 | 
				
			||||||
 | 
					Rs5BvCuXQpUV9N8UFfW8pAxYhLvUTqhf34m24syyFn9j1udEO1c+IeX7h7hX2CFL
 | 
				
			||||||
 | 
					+P6wS9Ok2Z++IKvhIXLy/OoBULxKXjM04aLxDDlRW3qEyeLKvbFiEHGSnlaDz27L
 | 
				
			||||||
 | 
					LBAGGRxzLLr0g1evV33AHUU2N8pATnzXHJaRiMjExjRi5IkHjbiEaxiqIwr8CSnS
 | 
				
			||||||
 | 
					4RlZ+owxhJ/4MjnsqBL3ELhkSnN+HGkPBQkbFDhCm0ICm78EK2x4+bWo/YUUfoky
 | 
				
			||||||
 | 
					Hq92XB6RNbO0RcdGyltFsJ02Ev20Hc4MClF7jT7xm7VJfbeYNmxZ6GNXZ7kEsl87
 | 
				
			||||||
 | 
					7qzFtr2BcEfw/ieyyoOrwAC9FBJc/9CALex3p3TGWpM43C+IdqZIsr9QHAzvJfY7
 | 
				
			||||||
 | 
					/n5/wJyCPhIZSSE3b8PZRIAdh6NA2IF877OCzIl2UFUNJE1zaEcTvjxZzCZ1SHGU
 | 
				
			||||||
 | 
					YzQeSbODHUuPDbhytBJnZW50b29AdGFzdHl0ZWEuZGWJAlQEEwEIAD4CGwMFCwkI
 | 
				
			||||||
 | 
					BwIGFQoJCAsCBBYCAwECHgECF4AWIQTVtMQ7SKh59jUpNuZZNG4Oo1xn5QUCXKjY
 | 
				
			||||||
 | 
					rQUJBUTnEgAKCRBZNG4Oo1xn5VhkD/42pGYstRMvrO37wJDnnLDm+ZPb0RGy80Ru
 | 
				
			||||||
 | 
					Nt3S6OmU3TFuU9mj/FBc8VNs6xr0CCMVVM/CXX1gXCHhADss1YDaOcRsl5wVJ6EF
 | 
				
			||||||
 | 
					tbpEXT/USMw3dV4Y8OYUSNxyEitzKt25CnOdWGPYaJG3YOtAR0qwopMiAgLrgLy9
 | 
				
			||||||
 | 
					mugXqnrykF7yN27i6iRi2Jk9K7tSb4owpw1kuToJrNGThAkz+3nvXG5oRiYFTlH3
 | 
				
			||||||
 | 
					pATx34r+QOg1o3giomP49cP4ohxvQFP90w2/cURhLqEKdR6N1X0bTXRQvy8G+4Wl
 | 
				
			||||||
 | 
					QMl8WYPzQUrKGMgj/f7Uhb3pFFLCcnCaYFdUj+fvshg5NMLGVztENz9x7Vr5n51o
 | 
				
			||||||
 | 
					Hj9WuM3s65orKrGhMUk4NJCsQWJUHnSNsEXsuir9ocwCv4unIJuoOukNJigL4d5o
 | 
				
			||||||
 | 
					i0fKPKuLpdIah1dmcrWLIoid0wPeA8unKQg3h6VL5KXpUudo8CiPw/kk1KTLtYQR
 | 
				
			||||||
 | 
					7lezb1oldqfWgGHmqnOK+u6sOhxGj2fcrTi4139ULMph+LCIB3JEtgaaw4lTTt0t
 | 
				
			||||||
 | 
					S8h6db6LalzsQyL2sIHgl/rmLmZ5sqZhmi/DsAjZWfpz+inUP6rgap+OgAmtLCit
 | 
				
			||||||
 | 
					BwsDAy7ux44mUNtW1KExuY2W/bmSLlV28H+fHJ3fhpHDQMNAFYc5n4NgTe6eT/KY
 | 
				
			||||||
 | 
					WA4KGfp7KYkCVAQTAQgAPhYhBNW0xDtIqHn2NSk25lk0bg6jXGflBQJcqNTKAhsD
 | 
				
			||||||
 | 
					BQkDw5EFBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEFk0bg6jXGflazAP/iae
 | 
				
			||||||
 | 
					7/PIaWhIyDw14NvyJG4D8FMSV9bC1cJ+ICo0qkx0dxcZMsxTp7fD8ODaSWzJEI4X
 | 
				
			||||||
 | 
					mGDvJp5fJ7ZALFhp7IBIsj9CHRWyVBCzwhnAXgSmGF+qzBFE7WjQORdn5ytTiWAN
 | 
				
			||||||
 | 
					PqyJV0sAw46jLJNvYv/LaFb2bzR/z6U1wQ2qvqXZj8vh2eLvY2XfQa1HnKaPi8h9
 | 
				
			||||||
 | 
					OqtLM80/6uai2scdYAI6usB8wxTJY2b2B8flDB7c8DruCDRL1QmrK5o70yIIai2c
 | 
				
			||||||
 | 
					4fXHHglulT9GnwD01a5DA2dgn5nxb81xgofgofXQjIOYARUKvcuZsF/tsR5S+C5k
 | 
				
			||||||
 | 
					CJnq8V9xdABbWz/FvwXz7ejf2jPtAnD6gcvuPnLX/dsxFHio2n4HHzXboUrVMKid
 | 
				
			||||||
 | 
					zcvuIrmlNtvKHYGxC9Dk3vNM+9rTlaY2BRt0zkgakDpMhqFu6A/TCEDZK0ukQLtc
 | 
				
			||||||
 | 
					h0g806AWding6gr4vQDeX6dSCuJMFKTu/2q85R1w2vGuyWYSm6QR6sM+KumOX3vJ
 | 
				
			||||||
 | 
					c/zvOodhRWXQBWYHTuSw6QGDCI115lWO8DAK4T6u7SVXfthHKm+38dpDH1tSfcHo
 | 
				
			||||||
 | 
					KaG7XJKExEPgdcNLvJIN/xCx5lX6fy0ohj7oF1dEpeBpIgqTC0l5I8bLAjcLKZl9
 | 
				
			||||||
 | 
					4YwJSSS8aTedptCmBTAHWd6y3W/hgFJrdKsqbHVGuQINBFlFJRsBEAC1EFjL9rvn
 | 
				
			||||||
 | 
					O9UIJ2dfaPdfm2GjH/sKfOInfWp4KKEDWtS59Pssld4gnjcmDNgunYYhHYcok61K
 | 
				
			||||||
 | 
					9J4x33KvkNAhEbw9y5AGW0tb7p2I6NxiOaWZjmZbg7AJMBFenipdUXBEjbu4LzEd
 | 
				
			||||||
 | 
					yyIm3/lQiV4bW6GR14cKdQLZm/inVmbEaGSpq2g19WA+X7SwBxzZR9O80Iohm3RL
 | 
				
			||||||
 | 
					X8Z8lXzUj/fUWCCstfXZwdy4vbZv8ms7kmq+3TUOwOiVavgWYhbal+nO0kLdVFbb
 | 
				
			||||||
 | 
					i7YRvZh6afxfgMyJ3v1goXvsW1W8jno2ikUmkwZiiPY/cKOPmOwEzj3hl73i6qrx
 | 
				
			||||||
 | 
					vm9SjEwEzI/gFXlJD8cOKMc6/g8kUeCepDfdKjgo1SYynLUk4NW9QeucJo6BSPEP
 | 
				
			||||||
 | 
					llamHsTaUGzT4tj9qZqAQ0dwSnWYvyi19EMCGssLoy7bAoNueHOYZtHN5TskKShQ
 | 
				
			||||||
 | 
					XzEG9IRZvXGmaWAT17sFesqXK0g47jQswmwobDsXyvXJfree36jQRj7SAVVK44Im
 | 
				
			||||||
 | 
					bqBe6BT9QYIBkfThAWjwTibg0P1CPGk5TPpssAQgM3jxXVEyD6iKCS4LKWrtm+Sk
 | 
				
			||||||
 | 
					MlGaPNyO8OcwHp6p5QaYAE6vlSfT8fsZ0iGd06ua5miZRbkM2i94/jVKvZLRvWv4
 | 
				
			||||||
 | 
					S8SMZemAYnVMc0YFWEJCbaKdZp35rb5e4QARAQABiQI8BBgBCAAmAhsMFiEE1bTE
 | 
				
			||||||
 | 
					O0ioefY1KTbmWTRuDqNcZ+UFAlyo2PAFCQVE51UACgkQWTRuDqNcZ+V+Hg/9HhVI
 | 
				
			||||||
 | 
					No0ID4o8y0jlhyNg8n/Fy08uDALQ6JlbN6buLw+IYU75GTDIysGjx+9bgt+Mjvtp
 | 
				
			||||||
 | 
					bbWkeT6okKkyB3H/x7w7v9GTYWlnzMA/KwHF7L7Wqy0afcVjg+fchWXPJQ3H5Jxh
 | 
				
			||||||
 | 
					bcX3FKkIN9kpfdHN87C8//s4LzDOWeYCxFwkxkbx4tc1K4HhezpvYDKiLmFMVbaU
 | 
				
			||||||
 | 
					qB0pzP8IM3hU1GJeAC2skfjstuaKJPuF895aFSF6++DYodXBFu3UlSJbJGfDEBYC
 | 
				
			||||||
 | 
					9PgSrxX1qlNUFw+6Hr2uSdPnmcKgCDFGhxB1d/Z2Xa/QFhvuj7U38eyqla3dzXxu
 | 
				
			||||||
 | 
					4+/9BOoJwdyRlUxd1Jcy3q7l8V4Hk1vMwKICdXBadAcAgSi0ImXt7UttpTYB7WNV
 | 
				
			||||||
 | 
					nlFmFFi8eVnmMll08LWV6LygG8GBSzW5NUZnUhxHbFVFcEuHo6W1lIEgJooOnGwd
 | 
				
			||||||
 | 
					H2rqKXpkcv86q7ODxdt9nb0txUPzgukusHes6Q0cnTMWcd0YT75frKjjK6TK8KZA
 | 
				
			||||||
 | 
					XMH0zobogpnr/n2ji87cn9sSlL3/2NtxfAwqyDWomECKOtKYfx10OPjrPrScDFG0
 | 
				
			||||||
 | 
					aF6w50Xg5DH/I38zzBVanEgwzWHosIVKNQHgoSYijErnShbRefA8+zCsyn0q/9Rg
 | 
				
			||||||
 | 
					cToAM7X3ro+tQQHWDIhiayHvJMeGN/R/u1U4Kv25BK4EW0zMChEMALGnffpA/rz6
 | 
				
			||||||
 | 
					oRXV++syFI6AaByfiatYgKh+d2LkhyeAAnp93VBV8c2YArsSp7XookhxlRA7XAGw
 | 
				
			||||||
 | 
					x71VKouHjdcMpZM76OcEJgC2fKCbsLrMhkjKOjux6Lru1mY4bFmXBxex0pssvIoc
 | 
				
			||||||
 | 
					zefV00qVvQ0e2JkvUmuKKIplyH0GAapDRnF3R8/doNNUXfVufHButKHlmK7yaFkK
 | 
				
			||||||
 | 
					UBXLFUc3c8mCm/UQcMrFYrlyRNd6Axir2LpD8ya8gIwOM49nH+DDSla4d23zP+4M
 | 
				
			||||||
 | 
					kTaWZ5QlX4FGN8kfPE4rzVxhCP0jtC5m2oqFp8dIKtxzX836YkHG7wlAPsaoPmhl
 | 
				
			||||||
 | 
					kJMylGSwvjRvjxNLHWodMJfrQgajnW0UEd1XrfO48i/OD3f1Z22/sHRY2VejD4KJ
 | 
				
			||||||
 | 
					49QBienKCUlNbZRfpaGOQn2HqbOX6/wUfS/83rhBVNrsU2kNb/+6OKsJV2YtokPK
 | 
				
			||||||
 | 
					saS88q8225YEcsDLPS/3V5VrFW0CQwXJM4AbVweHhE7486VtSfkQswEAjTMJSbTO
 | 
				
			||||||
 | 
					4IgjWYDaQ57m77bc4N9z0oCWaChlaAjdzSsL/0JQx5GJXUcxW1GvEGhP/Fx1IFd3
 | 
				
			||||||
 | 
					oCR8OmY6oZHYmB1fNvFLSmJN0dJcQjm3hebrSQiWg/JvVAlF2S7f+j0pjeki09kM
 | 
				
			||||||
 | 
					0RqAHOkDpLeY6ifU8+QW5DP5yh8d9ZDc4wjPdz53ycwJzaMqESOIr9eHYtOWN6Hi
 | 
				
			||||||
 | 
					0rItsMN8FB5A70te1IcKG5UWh3cCRg7fEbKVofIYTSU2V98RLkp+iEHLKfa6wObx
 | 
				
			||||||
 | 
					Mt60OVU/xbrO28w93cLpWUIH1Csow3k3wSbNmw3d9mWc7cVESct+IM5W4ZSYMcjG
 | 
				
			||||||
 | 
					cvcMELWCwuT1mPSkR0hv2oz5xFOBlUV1KUViIcxpKzTrjj69JAaBbJ3f5OEfEbj/
 | 
				
			||||||
 | 
					G+aa30EoddPBhwF7XnQUeC/DLRJQh2MH1ohMnkpBttDipHOuFS1CZh8xoxr/8moW
 | 
				
			||||||
 | 
					nj5FRG+FAZeCmcqj5PE+du7KF2XRPBlxhc1Nu+kPejlr6qa5qdwo4MzfuzmxWmvc
 | 
				
			||||||
 | 
					WQuNMtaPqQvYL1A09MH0uMH65MtJNsqbSvHa5AwAlletPw6Wr0qrBLBCmOpNf+Q7
 | 
				
			||||||
 | 
					7nBQBrK5VPMcto9IkGB4/bwhx7gQ0O2dD4dD4DPpGY9p52KpOG2ECoCWMtbsPD2P
 | 
				
			||||||
 | 
					bs+WNHN8V+3ZCxZukEj25wDhc5941P01BhKVFevGLHyYNWk34mQk7RdHj9OiEL8n
 | 
				
			||||||
 | 
					GpQ9l/R58+mvVwarzs898/y5onQieWi0Zu3WfMvjTOG3D3NIKMuthzRytfV5C/tJ
 | 
				
			||||||
 | 
					+W5ZX/jLVR3bzvzx8Pnpvf602xCST9/7LbgFhljfXQq0bq0d9si9hvyaMOh1PQFU
 | 
				
			||||||
 | 
					2+PzmWtHcsiVoyXfQp6ztJYFkoYaaD+Mc2jWG2Qy9kAyUGTXj/WfkPn7hr5hvuwk
 | 
				
			||||||
 | 
					0kNDSan8NY2f1mtG253qr6fMOmCgrUfaumpafd9xIJ65x1G2BGAr8bzjLJufEUaG
 | 
				
			||||||
 | 
					D2wBYWE6tlRqT4j7u6u9vRjShKH+A1UpLV2pEtaIQ3wfbt6GIwFJHWU506m3RCCn
 | 
				
			||||||
 | 
					pL46fAOVKS1GSuf79koXsZeECJRSbipXz3TJs0TqiQKzBBgBCAAmAhsCFiEE1bTE
 | 
				
			||||||
 | 
					O0ioefY1KTbmWTRuDqNcZ+UFAlyo2PAFCQM9QGYAgXYgBBkRCAAdFiEENVUmaGTK
 | 
				
			||||||
 | 
					bX/0Wqbnz8OUl/GybgcFAltMzAoACgkQz8OUl/Gybgf0OwD/c4hwqsfZ79t7pM9d
 | 
				
			||||||
 | 
					PPWYQ1jyq2g3ELMKyPp79GmL0qsA/2t2qkaOEX3y7egmhL/iKyqASb4y/JTABGMU
 | 
				
			||||||
 | 
					hy5GjBhxCRBZNG4Oo1xn5WBvEACbCAQRC00FYoktuRzQQy2LCJe13AUS1/lCWv8B
 | 
				
			||||||
 | 
					Qu7hTmM8TC/iNmYk71qeYInQMp/12b0HSWcv8IBmOlMy2GTjgnTgiwpqY5nhtb9O
 | 
				
			||||||
 | 
					uB5H2g6fpu7FFG9ARhtH9PiTMwOUzfZFUz0tDdEEG5sayzWUcY3zjmJFmHSg5A9B
 | 
				
			||||||
 | 
					/Q/yctqZ1eINtyEECINo/OVEfD7bmyZwK/vrxAg285iF6lB11wVl+5E7sNy9Hvu8
 | 
				
			||||||
 | 
					4kCKPksqyjFWUd0XoEu9AH6+XVeEPF7CQKHpRfhc4uweT9O5nTb7aaPcqq0B4lUL
 | 
				
			||||||
 | 
					unG6KSCm88zaZczp2SUCFwENegmBT/YKN5ZoHsPh1nwLxh194EP/qRjW9IvFKTlJ
 | 
				
			||||||
 | 
					EsB4uCpfDeC233oH5nDkvvphcPYdUuOsVH1uPQ7PyWNTf1ufd9bDSDtK8epIcDPe
 | 
				
			||||||
 | 
					abOuphxQbrMVP4JJsBXnVW5raZO7s5lmSA8Ovce//+xJSAq9u0GTsGu1hWDe60ro
 | 
				
			||||||
 | 
					uOZwqjo/cU5G4y7WHRaC3oshH+DO8ajdXDogoDVs8DzYkTfWND2DDNEVhVrn7lGf
 | 
				
			||||||
 | 
					a4739sFIDagtBq6RzJGL0X82eJZzXPFiYvmy0OVbNDUgH+Drva/wRv/tN8RvBiS6
 | 
				
			||||||
 | 
					bsn8+GBGaU5RASu67UbqxHiytFnN4OnADA5ZHcwQbMgRHHiiMMIf+tJWH/pFMp00
 | 
				
			||||||
 | 
					epiDVQ==
 | 
				
			||||||
 | 
					=VSKJ
 | 
				
			||||||
 | 
					-----END PGP PUBLIC KEY BLOCK-----
 | 
				
			||||||
 | 
					`
 | 
				
			||||||
 | 
						ekey, err := checkArmoredGPGKeyString(testIssue6599)
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
						expire := getExpiryTime(ekey)
 | 
				
			||||||
 | 
						assert.Equal(t, time.Unix(1586105389, 0), expire)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -118,17 +118,25 @@ func mailIssueCommentToParticipants(e Engine, issue *Issue, doer *User, content
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// MailParticipants sends new issue thread created emails to repository watchers
 | 
					// MailParticipants sends new issue thread created emails to repository watchers
 | 
				
			||||||
// and mentioned people.
 | 
					// and mentioned people.
 | 
				
			||||||
func (issue *Issue) MailParticipants() (err error) {
 | 
					func (issue *Issue) MailParticipants(opType ActionType) (err error) {
 | 
				
			||||||
	return issue.mailParticipants(x)
 | 
						return issue.mailParticipants(x, opType)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (issue *Issue) mailParticipants(e Engine) (err error) {
 | 
					func (issue *Issue) mailParticipants(e Engine, opType ActionType) (err error) {
 | 
				
			||||||
	mentions := markup.FindAllMentions(issue.Content)
 | 
						mentions := markup.FindAllMentions(issue.Content)
 | 
				
			||||||
	if err = UpdateIssueMentions(e, issue.ID, mentions); err != nil {
 | 
						if err = UpdateIssueMentions(e, issue.ID, mentions); err != nil {
 | 
				
			||||||
		return fmt.Errorf("UpdateIssueMentions [%d]: %v", issue.ID, err)
 | 
							return fmt.Errorf("UpdateIssueMentions [%d]: %v", issue.ID, err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err = mailIssueCommentToParticipants(e, issue, issue.Poster, issue.Content, nil, mentions); err != nil {
 | 
						var content = issue.Content
 | 
				
			||||||
 | 
						switch opType {
 | 
				
			||||||
 | 
						case ActionCloseIssue, ActionClosePullRequest:
 | 
				
			||||||
 | 
							content = fmt.Sprintf("Closed #%d", issue.Index)
 | 
				
			||||||
 | 
						case ActionReopenIssue, ActionReopenPullRequest:
 | 
				
			||||||
 | 
							content = fmt.Sprintf("Reopened #%d", issue.Index)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = mailIssueCommentToParticipants(e, issue, issue.Poster, content, nil, mentions); err != nil {
 | 
				
			||||||
		log.Error(4, "mailIssueCommentToParticipants: %v", err)
 | 
							log.Error(4, "mailIssueCommentToParticipants: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -616,9 +616,9 @@ func ExternalUserLogin(user *User, login, password string, source *LoginSource,
 | 
				
			|||||||
		return nil, err
 | 
							return nil, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if !user.IsActive {
 | 
						// WARN: DON'T check user.IsActive, that will be checked on reqSign so that
 | 
				
			||||||
		return nil, ErrUserInactive{user.ID, user.Name}
 | 
						// user could be hint to resend confirm email.
 | 
				
			||||||
	} else if user.ProhibitLogin {
 | 
						if user.ProhibitLogin {
 | 
				
			||||||
		return nil, ErrUserProhibitLogin{user.ID, user.Name}
 | 
							return nil, ErrUserProhibitLogin{user.ID, user.Name}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -658,9 +658,9 @@ func UserSignIn(username, password string) (*User, error) {
 | 
				
			|||||||
		switch user.LoginType {
 | 
							switch user.LoginType {
 | 
				
			||||||
		case LoginNoType, LoginPlain, LoginOAuth2:
 | 
							case LoginNoType, LoginPlain, LoginOAuth2:
 | 
				
			||||||
			if user.IsPasswordSet() && user.ValidatePassword(password) {
 | 
								if user.IsPasswordSet() && user.ValidatePassword(password) {
 | 
				
			||||||
				if !user.IsActive {
 | 
									// WARN: DON'T check user.IsActive, that will be checked on reqSign so that
 | 
				
			||||||
					return nil, ErrUserInactive{user.ID, user.Name}
 | 
									// user could be hint to resend confirm email.
 | 
				
			||||||
				} else if user.ProhibitLogin {
 | 
									if user.ProhibitLogin {
 | 
				
			||||||
					return nil, ErrUserProhibitLogin{user.ID, user.Name}
 | 
										return nil, ErrUserProhibitLogin{user.ID, user.Name}
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,8 +5,11 @@
 | 
				
			|||||||
package migrations
 | 
					package migrations
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/git"
 | 
						"code.gitea.io/git"
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/go-xorm/xorm"
 | 
						"github.com/go-xorm/xorm"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -18,6 +21,17 @@ func fixReleaseSha1OnReleaseTable(x *xorm.Engine) error {
 | 
				
			|||||||
		TagName string
 | 
							TagName string
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						type Repository struct {
 | 
				
			||||||
 | 
							ID      int64
 | 
				
			||||||
 | 
							OwnerID int64
 | 
				
			||||||
 | 
							Name    string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						type User struct {
 | 
				
			||||||
 | 
							ID   int64
 | 
				
			||||||
 | 
							Name string
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Update release sha1
 | 
						// Update release sha1
 | 
				
			||||||
	const batchSize = 100
 | 
						const batchSize = 100
 | 
				
			||||||
	sess := x.NewSession()
 | 
						sess := x.NewSession()
 | 
				
			||||||
@@ -27,7 +41,8 @@ func fixReleaseSha1OnReleaseTable(x *xorm.Engine) error {
 | 
				
			|||||||
		err          error
 | 
							err          error
 | 
				
			||||||
		count        int
 | 
							count        int
 | 
				
			||||||
		gitRepoCache = make(map[int64]*git.Repository)
 | 
							gitRepoCache = make(map[int64]*git.Repository)
 | 
				
			||||||
		repoCache    = make(map[int64]*models.Repository)
 | 
							repoCache    = make(map[int64]*Repository)
 | 
				
			||||||
 | 
							userCache    = make(map[int64]*User)
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err = sess.Begin(); err != nil {
 | 
						if err = sess.Begin(); err != nil {
 | 
				
			||||||
@@ -48,14 +63,31 @@ func fixReleaseSha1OnReleaseTable(x *xorm.Engine) error {
 | 
				
			|||||||
			if !ok {
 | 
								if !ok {
 | 
				
			||||||
				repo, ok := repoCache[release.RepoID]
 | 
									repo, ok := repoCache[release.RepoID]
 | 
				
			||||||
				if !ok {
 | 
									if !ok {
 | 
				
			||||||
					repo, err = models.GetRepositoryByID(release.RepoID)
 | 
										repo = new(Repository)
 | 
				
			||||||
 | 
										has, err := sess.ID(release.RepoID).Get(repo)
 | 
				
			||||||
					if err != nil {
 | 
										if err != nil {
 | 
				
			||||||
						return err
 | 
											return err
 | 
				
			||||||
 | 
										} else if !has {
 | 
				
			||||||
 | 
											return fmt.Errorf("Repository %d is not exist", release.RepoID)
 | 
				
			||||||
					}
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					repoCache[release.RepoID] = repo
 | 
										repoCache[release.RepoID] = repo
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
				gitRepo, err = git.OpenRepository(repo.RepoPath())
 | 
									user, ok := userCache[repo.OwnerID]
 | 
				
			||||||
 | 
									if !ok {
 | 
				
			||||||
 | 
										user = new(User)
 | 
				
			||||||
 | 
										has, err := sess.ID(repo.OwnerID).Get(user)
 | 
				
			||||||
 | 
										if err != nil {
 | 
				
			||||||
 | 
											return err
 | 
				
			||||||
 | 
										} else if !has {
 | 
				
			||||||
 | 
											return fmt.Errorf("User %d is not exist", repo.OwnerID)
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										userCache[repo.OwnerID] = user
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									gitRepo, err = git.OpenRepository(models.RepoPath(user.Name, repo.Name))
 | 
				
			||||||
				if err != nil {
 | 
									if err != nil {
 | 
				
			||||||
					return err
 | 
										return err
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
@@ -63,12 +95,14 @@ func fixReleaseSha1OnReleaseTable(x *xorm.Engine) error {
 | 
				
			|||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			release.Sha1, err = gitRepo.GetTagCommitID(release.TagName)
 | 
								release.Sha1, err = gitRepo.GetTagCommitID(release.TagName)
 | 
				
			||||||
			if err != nil {
 | 
								if err != nil && !git.IsErrNotExist(err) {
 | 
				
			||||||
				return err
 | 
									return err
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if _, err = sess.ID(release.ID).Cols("sha1").Update(release); err != nil {
 | 
								if err == nil {
 | 
				
			||||||
				return err
 | 
									if _, err = sess.ID(release.ID).Cols("sha1").Update(release); err != nil {
 | 
				
			||||||
 | 
										return err
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			count++
 | 
								count++
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -51,8 +51,9 @@ type Engine interface {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
	x      *xorm.Engine
 | 
						x                  *xorm.Engine
 | 
				
			||||||
	tables []interface{}
 | 
						supportedDatabases = []string{"mysql", "postgres", "mssql"}
 | 
				
			||||||
 | 
						tables             []interface{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// HasEngine specifies if we have a xorm.Engine
 | 
						// HasEngine specifies if we have a xorm.Engine
 | 
				
			||||||
	HasEngine bool
 | 
						HasEngine bool
 | 
				
			||||||
@@ -350,7 +351,9 @@ func Ping() error {
 | 
				
			|||||||
func DumpDatabase(filePath string, dbType string) error {
 | 
					func DumpDatabase(filePath string, dbType string) error {
 | 
				
			||||||
	var tbs []*core.Table
 | 
						var tbs []*core.Table
 | 
				
			||||||
	for _, t := range tables {
 | 
						for _, t := range tables {
 | 
				
			||||||
		tbs = append(tbs, x.TableInfo(t).Table)
 | 
							t := x.TableInfo(t)
 | 
				
			||||||
 | 
							t.Table.Name = t.Name
 | 
				
			||||||
 | 
							tbs = append(tbs, t.Table)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if len(dbType) > 0 {
 | 
						if len(dbType) > 0 {
 | 
				
			||||||
		return x.DumpTablesToFile(tbs, filePath, core.DbType(dbType))
 | 
							return x.DumpTablesToFile(tbs, filePath, core.DbType(dbType))
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,4 +12,5 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func init() {
 | 
					func init() {
 | 
				
			||||||
	EnableSQLite3 = true
 | 
						EnableSQLite3 = true
 | 
				
			||||||
 | 
						supportedDatabases = append(supportedDatabases, "sqlite3")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,6 +6,9 @@
 | 
				
			|||||||
package models
 | 
					package models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
 | 
						"os"
 | 
				
			||||||
 | 
						"path/filepath"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/stretchr/testify/assert"
 | 
						"github.com/stretchr/testify/assert"
 | 
				
			||||||
@@ -93,3 +96,14 @@ func Test_getPostgreSQLConnectionString(t *testing.T) {
 | 
				
			|||||||
		assert.Equal(t, test.Output, connStr)
 | 
							assert.Equal(t, test.Output, connStr)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func TestDumpDatabase(t *testing.T) {
 | 
				
			||||||
 | 
						assert.NoError(t, PrepareTestDatabase())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						dir, err := ioutil.TempDir(os.TempDir(), "dump")
 | 
				
			||||||
 | 
						assert.NoError(t, err)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for _, dbType := range supportedDatabases {
 | 
				
			||||||
 | 
							assert.NoError(t, DumpDatabase(filepath.Join(dir, dbType+".sql"), dbType))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,6 +33,11 @@ type Team struct {
 | 
				
			|||||||
	Units       []*TeamUnit `xorm:"-"`
 | 
						Units       []*TeamUnit `xorm:"-"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetUnits return a list of available units for a team
 | 
				
			||||||
 | 
					func (t *Team) GetUnits() error {
 | 
				
			||||||
 | 
						return t.getUnits(x)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (t *Team) getUnits(e Engine) (err error) {
 | 
					func (t *Team) getUnits(e Engine) (err error) {
 | 
				
			||||||
	if t.Units != nil {
 | 
						if t.Units != nil {
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1077,9 +1077,11 @@ func CleanUpMigrateInfo(repo *Repository) (*Repository, error) {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if err := cleanUpMigrateGitConfig(repo.GitConfigPath()); err != nil {
 | 
						_, err := git.NewCommand("remote", "remove", "origin").RunInDir(repoPath)
 | 
				
			||||||
		return repo, fmt.Errorf("cleanUpMigrateGitConfig: %v", err)
 | 
						if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") {
 | 
				
			||||||
 | 
							return repo, fmt.Errorf("CleanUpMigrateInfo: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if repo.HasWiki() {
 | 
						if repo.HasWiki() {
 | 
				
			||||||
		if err := cleanUpMigrateGitConfig(path.Join(repo.WikiPath(), "config")); err != nil {
 | 
							if err := cleanUpMigrateGitConfig(path.Join(repo.WikiPath(), "config")); err != nil {
 | 
				
			||||||
			return repo, fmt.Errorf("cleanUpMigrateGitConfig (wiki): %v", err)
 | 
								return repo, fmt.Errorf("cleanUpMigrateGitConfig (wiki): %v", err)
 | 
				
			||||||
@@ -2430,6 +2432,7 @@ func ForkRepository(doer, u *User, oldRepo *Repository, name, desc string) (_ *R
 | 
				
			|||||||
		Description:   desc,
 | 
							Description:   desc,
 | 
				
			||||||
		DefaultBranch: oldRepo.DefaultBranch,
 | 
							DefaultBranch: oldRepo.DefaultBranch,
 | 
				
			||||||
		IsPrivate:     oldRepo.IsPrivate,
 | 
							IsPrivate:     oldRepo.IsPrivate,
 | 
				
			||||||
 | 
							IsEmpty:       oldRepo.IsEmpty,
 | 
				
			||||||
		IsFork:        true,
 | 
							IsFork:        true,
 | 
				
			||||||
		ForkID:        oldRepo.ID,
 | 
							ForkID:        oldRepo.ID,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,7 +20,6 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"github.com/Unknwon/com"
 | 
						"github.com/Unknwon/com"
 | 
				
			||||||
	"github.com/go-xorm/xorm"
 | 
						"github.com/go-xorm/xorm"
 | 
				
			||||||
	"gopkg.in/ini.v1"
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// MirrorQueue holds an UniqueQueue object of the mirror
 | 
					// MirrorQueue holds an UniqueQueue object of the mirror
 | 
				
			||||||
@@ -71,11 +70,18 @@ func (m *Mirror) ScheduleNextUpdate() {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func remoteAddress(repoPath string) (string, error) {
 | 
					func remoteAddress(repoPath string) (string, error) {
 | 
				
			||||||
	cfg, err := ini.Load(GitConfigPath(repoPath))
 | 
						cmd := git.NewCommand("remote", "get-url", "origin")
 | 
				
			||||||
 | 
						result, err := cmd.RunInDir(repoPath)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") {
 | 
				
			||||||
 | 
								return "", nil
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		return "", err
 | 
							return "", err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return cfg.Section("remote \"origin\"").Key("url").Value(), nil
 | 
						if len(result) > 0 {
 | 
				
			||||||
 | 
							return result[:len(result)-1], nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return "", nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (m *Mirror) readAddress() {
 | 
					func (m *Mirror) readAddress() {
 | 
				
			||||||
@@ -115,14 +121,15 @@ func (m *Mirror) FullAddress() string {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// SaveAddress writes new address to Git repository config.
 | 
					// SaveAddress writes new address to Git repository config.
 | 
				
			||||||
func (m *Mirror) SaveAddress(addr string) error {
 | 
					func (m *Mirror) SaveAddress(addr string) error {
 | 
				
			||||||
	configPath := m.Repo.GitConfigPath()
 | 
						repoPath := m.Repo.RepoPath()
 | 
				
			||||||
	cfg, err := ini.Load(configPath)
 | 
						// Remove old origin
 | 
				
			||||||
	if err != nil {
 | 
						_, err := git.NewCommand("remote", "remove", "origin").RunInDir(repoPath)
 | 
				
			||||||
		return fmt.Errorf("Load: %v", err)
 | 
						if err != nil && !strings.HasPrefix(err.Error(), "exit status 128 - fatal: No such remote ") {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cfg.Section("remote \"origin\"").Key("url").SetValue(addr)
 | 
						_, err = git.NewCommand("remote", "add", "origin", addr).RunInDir(repoPath)
 | 
				
			||||||
	return cfg.SaveToIndent(configPath, "\t")
 | 
						return err
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// gitShortEmptySha Git short empty SHA
 | 
					// gitShortEmptySha Git short empty SHA
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -238,7 +238,7 @@ func accessLevelUnit(e Engine, user *User, repo *Repository, unitType UnitType)
 | 
				
			|||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return AccessModeNone, err
 | 
							return AccessModeNone, err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return perm.UnitAccessMode(UnitTypeCode), nil
 | 
						return perm.UnitAccessMode(unitType), nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func hasAccessUnit(e Engine, user *User, repo *Repository, unitType UnitType, testMode AccessMode) (bool, error) {
 | 
					func hasAccessUnit(e Engine, user *User, repo *Repository, unitType UnitType, testMode AccessMode) (bool, error) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -214,9 +214,10 @@ func SignedInUser(ctx *macaron.Context, sess session.Store) (*models.User, bool)
 | 
				
			|||||||
					}
 | 
										}
 | 
				
			||||||
					return nil, false
 | 
										return nil, false
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									ctx.Data["IsApiToken"] = true
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			ctx.Data["IsApiToken"] = true
 | 
					 | 
				
			||||||
			return u, true
 | 
								return u, true
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -168,16 +168,15 @@ func (f *GrantApplicationForm) Validate(ctx *macaron.Context, errs binding.Error
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// AccessTokenForm for issuing access tokens from authorization codes or refresh tokens
 | 
					// AccessTokenForm for issuing access tokens from authorization codes or refresh tokens
 | 
				
			||||||
type AccessTokenForm struct {
 | 
					type AccessTokenForm struct {
 | 
				
			||||||
	GrantType    string
 | 
						GrantType    string `json:"grant_type"`
 | 
				
			||||||
	ClientID     string
 | 
						ClientID     string `json:"client_id"`
 | 
				
			||||||
	ClientSecret string
 | 
						ClientSecret string `json:"client_secret"`
 | 
				
			||||||
	RedirectURI  string
 | 
						RedirectURI  string `json:"redirect_uri"`
 | 
				
			||||||
	// TODO Specify authentication code length to prevent against birthday attacks
 | 
						Code         string `json:"code"`
 | 
				
			||||||
	Code         string
 | 
						RefreshToken string `json:"refresh_token"`
 | 
				
			||||||
	RefreshToken string
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// PKCE support
 | 
						// PKCE support
 | 
				
			||||||
	CodeVerifier string
 | 
						CodeVerifier string `json:"code_verifier"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Validate valideates the fields
 | 
					// Validate valideates the fields
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -110,6 +110,28 @@ func (ctx *APIContext) RequireCSRF() {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// CheckForOTP validateds OTP
 | 
				
			||||||
 | 
					func (ctx *APIContext) CheckForOTP() {
 | 
				
			||||||
 | 
						otpHeader := ctx.Req.Header.Get("X-Gitea-OTP")
 | 
				
			||||||
 | 
						twofa, err := models.GetTwoFactorByUID(ctx.Context.User.ID)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							if models.IsErrTwoFactorNotEnrolled(err) {
 | 
				
			||||||
 | 
								return // No 2FA enrollment for this user
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							ctx.Context.Error(500)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ok, err := twofa.ValidateTOTP(otpHeader)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							ctx.Context.Error(500)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							ctx.Context.Error(401)
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// APIContexter returns apicontext as macaron middleware
 | 
					// APIContexter returns apicontext as macaron middleware
 | 
				
			||||||
func APIContexter() macaron.Handler {
 | 
					func APIContexter() macaron.Handler {
 | 
				
			||||||
	return func(c *Context) {
 | 
						return func(c *Context) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,12 @@
 | 
				
			|||||||
// Copyright 2014 The Gogs Authors. All rights reserved.
 | 
					// Copyright 2014 The Gogs Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
				
			||||||
// Use of this source code is governed by a MIT-style
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
// license that can be found in the LICENSE file.
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
package context
 | 
					package context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/auth"
 | 
						"code.gitea.io/gitea/modules/auth"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
@@ -88,6 +90,28 @@ func Toggle(options *ToggleOptions) macaron.Handler {
 | 
				
			|||||||
				ctx.HTML(200, "user/auth/activate")
 | 
									ctx.HTML(200, "user/auth/activate")
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
								if ctx.IsSigned && auth.IsAPIPath(ctx.Req.URL.Path) && ctx.IsBasicAuth {
 | 
				
			||||||
 | 
									twofa, err := models.GetTwoFactorByUID(ctx.User.ID)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										if models.IsErrTwoFactorNotEnrolled(err) {
 | 
				
			||||||
 | 
											return // No 2FA enrollment for this user
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
										ctx.Error(500)
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									otpHeader := ctx.Req.Header.Get("X-Gitea-OTP")
 | 
				
			||||||
 | 
									ok, err := twofa.ValidateTOTP(otpHeader)
 | 
				
			||||||
 | 
									if err != nil {
 | 
				
			||||||
 | 
										ctx.Error(500)
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
									if !ok {
 | 
				
			||||||
 | 
										ctx.JSON(403, map[string]string{
 | 
				
			||||||
 | 
											"message": "Only signed in user is allowed to call APIs.",
 | 
				
			||||||
 | 
										})
 | 
				
			||||||
 | 
										return
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// Redirect to log in page if auto-signin info is provided and has not signed in.
 | 
							// Redirect to log in page if auto-signin info is provided and has not signed in.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -396,6 +396,13 @@ func RepoAssignment() macaron.Handler {
 | 
				
			|||||||
			ctx.Data["IsStaringRepo"] = models.IsStaring(ctx.User.ID, repo.ID)
 | 
								ctx.Data["IsStaringRepo"] = models.IsStaring(ctx.User.ID, repo.ID)
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if repo.IsFork {
 | 
				
			||||||
 | 
								RetrieveBaseRepo(ctx, repo)
 | 
				
			||||||
 | 
								if ctx.Written() {
 | 
				
			||||||
 | 
									return
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// repo is empty and display enable
 | 
							// repo is empty and display enable
 | 
				
			||||||
		if ctx.Repo.Repository.IsEmpty {
 | 
							if ctx.Repo.Repository.IsEmpty {
 | 
				
			||||||
			ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
 | 
								ctx.Data["BranchName"] = ctx.Repo.Repository.DefaultBranch
 | 
				
			||||||
@@ -423,13 +430,6 @@ func RepoAssignment() macaron.Handler {
 | 
				
			|||||||
		ctx.Data["BranchName"] = ctx.Repo.BranchName
 | 
							ctx.Data["BranchName"] = ctx.Repo.BranchName
 | 
				
			||||||
		ctx.Data["CommitID"] = ctx.Repo.CommitID
 | 
							ctx.Data["CommitID"] = ctx.Repo.CommitID
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if repo.IsFork {
 | 
					 | 
				
			||||||
			RetrieveBaseRepo(ctx, repo)
 | 
					 | 
				
			||||||
			if ctx.Written() {
 | 
					 | 
				
			||||||
				return
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		// People who have push access or have forked repository can propose a new pull request.
 | 
							// People who have push access or have forked repository can propose a new pull request.
 | 
				
			||||||
		if ctx.Repo.CanWrite(models.UnitTypeCode) || (ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID)) {
 | 
							if ctx.Repo.CanWrite(models.UnitTypeCode) || (ctx.IsSigned && ctx.User.HasForkedRepo(ctx.Repo.Repository.ID)) {
 | 
				
			||||||
			// Pull request is allowed if this is a fork repository
 | 
								// Pull request is allowed if this is a fork repository
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -152,15 +152,15 @@ func (p *postProcessError) Error() string {
 | 
				
			|||||||
type processor func(ctx *postProcessCtx, node *html.Node)
 | 
					type processor func(ctx *postProcessCtx, node *html.Node)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var defaultProcessors = []processor{
 | 
					var defaultProcessors = []processor{
 | 
				
			||||||
	mentionProcessor,
 | 
					 | 
				
			||||||
	shortLinkProcessor,
 | 
					 | 
				
			||||||
	fullIssuePatternProcessor,
 | 
						fullIssuePatternProcessor,
 | 
				
			||||||
 | 
						fullSha1PatternProcessor,
 | 
				
			||||||
 | 
						shortLinkProcessor,
 | 
				
			||||||
 | 
						linkProcessor,
 | 
				
			||||||
 | 
						mentionProcessor,
 | 
				
			||||||
	issueIndexPatternProcessor,
 | 
						issueIndexPatternProcessor,
 | 
				
			||||||
	crossReferenceIssueIndexPatternProcessor,
 | 
						crossReferenceIssueIndexPatternProcessor,
 | 
				
			||||||
	fullSha1PatternProcessor,
 | 
					 | 
				
			||||||
	sha1CurrentPatternProcessor,
 | 
						sha1CurrentPatternProcessor,
 | 
				
			||||||
	emailAddressProcessor,
 | 
						emailAddressProcessor,
 | 
				
			||||||
	linkProcessor,
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type postProcessCtx struct {
 | 
					type postProcessCtx struct {
 | 
				
			||||||
@@ -194,14 +194,14 @@ func PostProcess(
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var commitMessageProcessors = []processor{
 | 
					var commitMessageProcessors = []processor{
 | 
				
			||||||
	mentionProcessor,
 | 
					 | 
				
			||||||
	fullIssuePatternProcessor,
 | 
						fullIssuePatternProcessor,
 | 
				
			||||||
 | 
						fullSha1PatternProcessor,
 | 
				
			||||||
 | 
						linkProcessor,
 | 
				
			||||||
 | 
						mentionProcessor,
 | 
				
			||||||
	issueIndexPatternProcessor,
 | 
						issueIndexPatternProcessor,
 | 
				
			||||||
	crossReferenceIssueIndexPatternProcessor,
 | 
						crossReferenceIssueIndexPatternProcessor,
 | 
				
			||||||
	fullSha1PatternProcessor,
 | 
					 | 
				
			||||||
	sha1CurrentPatternProcessor,
 | 
						sha1CurrentPatternProcessor,
 | 
				
			||||||
	emailAddressProcessor,
 | 
						emailAddressProcessor,
 | 
				
			||||||
	linkProcessor,
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// RenderCommitMessage will use the same logic as PostProcess, but will disable
 | 
					// RenderCommitMessage will use the same logic as PostProcess, but will disable
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -113,6 +113,12 @@ func TestRender_links(t *testing.T) {
 | 
				
			|||||||
	test(
 | 
						test(
 | 
				
			||||||
		"https://foo_bar.example.com/",
 | 
							"https://foo_bar.example.com/",
 | 
				
			||||||
		`<p><a href="https://foo_bar.example.com/" rel="nofollow">https://foo_bar.example.com/</a></p>`)
 | 
							`<p><a href="https://foo_bar.example.com/" rel="nofollow">https://foo_bar.example.com/</a></p>`)
 | 
				
			||||||
 | 
						test(
 | 
				
			||||||
 | 
							"https://stackoverflow.com/questions/2896191/what-is-go-used-fore",
 | 
				
			||||||
 | 
							`<p><a href="https://stackoverflow.com/questions/2896191/what-is-go-used-fore" rel="nofollow">https://stackoverflow.com/questions/2896191/what-is-go-used-fore</a></p>`)
 | 
				
			||||||
 | 
						test(
 | 
				
			||||||
 | 
							"https://username:password@gitea.com",
 | 
				
			||||||
 | 
							`<p><a href="https://username:password@gitea.com" rel="nofollow">https://username:password@gitea.com</a></p>`)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Test that should *not* be turned into URL
 | 
						// Test that should *not* be turned into URL
 | 
				
			||||||
	test(
 | 
						test(
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -42,19 +42,34 @@ func (m *mailNotifier) NotifyCreateIssueComment(doer *models.User, repo *models.
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (m *mailNotifier) NotifyNewIssue(issue *models.Issue) {
 | 
					func (m *mailNotifier) NotifyNewIssue(issue *models.Issue) {
 | 
				
			||||||
	if err := issue.MailParticipants(); err != nil {
 | 
						if err := issue.MailParticipants(models.ActionCreateIssue); err != nil {
 | 
				
			||||||
		log.Error(4, "MailParticipants: %v", err)
 | 
							log.Error(4, "MailParticipants: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (m *mailNotifier) NotifyIssueChangeStatus(doer *models.User, issue *models.Issue, isClosed bool) {
 | 
					func (m *mailNotifier) NotifyIssueChangeStatus(doer *models.User, issue *models.Issue, isClosed bool) {
 | 
				
			||||||
	if err := issue.MailParticipants(); err != nil {
 | 
						var actionType models.ActionType
 | 
				
			||||||
 | 
						if issue.IsPull {
 | 
				
			||||||
 | 
							if isClosed {
 | 
				
			||||||
 | 
								actionType = models.ActionClosePullRequest
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								actionType = models.ActionReopenPullRequest
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							if isClosed {
 | 
				
			||||||
 | 
								actionType = models.ActionCloseIssue
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								actionType = models.ActionReopenIssue
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err := issue.MailParticipants(actionType); err != nil {
 | 
				
			||||||
		log.Error(4, "MailParticipants: %v", err)
 | 
							log.Error(4, "MailParticipants: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (m *mailNotifier) NotifyNewPullRequest(pr *models.PullRequest) {
 | 
					func (m *mailNotifier) NotifyNewPullRequest(pr *models.PullRequest) {
 | 
				
			||||||
	if err := pr.Issue.MailParticipants(); err != nil {
 | 
						if err := pr.Issue.MailParticipants(models.ActionCreatePullRequest); err != nil {
 | 
				
			||||||
		log.Error(4, "MailParticipants: %v", err)
 | 
							log.Error(4, "MailParticipants: %v", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										216
									
								
								modules/session/memory.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										216
									
								
								modules/session/memory.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,216 @@
 | 
				
			|||||||
 | 
					// Copyright 2013 Beego Authors
 | 
				
			||||||
 | 
					// Copyright 2014 The Macaron Authors
 | 
				
			||||||
 | 
					// Copyright 2019 The Gitea Authors
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Licensed under the Apache License, Version 2.0 (the "License"): you may
 | 
				
			||||||
 | 
					// not use this file except in compliance with the License. You may obtain
 | 
				
			||||||
 | 
					// a copy of the License at
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//     http://www.apache.org/licenses/LICENSE-2.0
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Unless required by applicable law or agreed to in writing, software
 | 
				
			||||||
 | 
					// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 | 
				
			||||||
 | 
					// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 | 
				
			||||||
 | 
					// License for the specific language governing permissions and limitations
 | 
				
			||||||
 | 
					// under the License.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package session
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"container/list"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-macaron/session"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MemStore represents a in-memory session store implementation.
 | 
				
			||||||
 | 
					type MemStore struct {
 | 
				
			||||||
 | 
						sid        string
 | 
				
			||||||
 | 
						lock       sync.RWMutex
 | 
				
			||||||
 | 
						data       map[interface{}]interface{}
 | 
				
			||||||
 | 
						lastAccess time.Time
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewMemStore creates and returns a memory session store.
 | 
				
			||||||
 | 
					func NewMemStore(sid string) *MemStore {
 | 
				
			||||||
 | 
						return &MemStore{
 | 
				
			||||||
 | 
							sid:        sid,
 | 
				
			||||||
 | 
							data:       make(map[interface{}]interface{}),
 | 
				
			||||||
 | 
							lastAccess: time.Now(),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Set sets value to given key in session.
 | 
				
			||||||
 | 
					func (s *MemStore) Set(key, val interface{}) error {
 | 
				
			||||||
 | 
						s.lock.Lock()
 | 
				
			||||||
 | 
						defer s.lock.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s.data[key] = val
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Get gets value by given key in session.
 | 
				
			||||||
 | 
					func (s *MemStore) Get(key interface{}) interface{} {
 | 
				
			||||||
 | 
						s.lock.RLock()
 | 
				
			||||||
 | 
						defer s.lock.RUnlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return s.data[key]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Delete deletes a key from session.
 | 
				
			||||||
 | 
					func (s *MemStore) Delete(key interface{}) error {
 | 
				
			||||||
 | 
						s.lock.Lock()
 | 
				
			||||||
 | 
						defer s.lock.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						delete(s.data, key)
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ID returns current session ID.
 | 
				
			||||||
 | 
					func (s *MemStore) ID() string {
 | 
				
			||||||
 | 
						return s.sid
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Release releases resource and save data to provider.
 | 
				
			||||||
 | 
					func (*MemStore) Release() error {
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Flush deletes all session data.
 | 
				
			||||||
 | 
					func (s *MemStore) Flush() error {
 | 
				
			||||||
 | 
						s.lock.Lock()
 | 
				
			||||||
 | 
						defer s.lock.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s.data = make(map[interface{}]interface{})
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// MemProvider represents a in-memory session provider implementation.
 | 
				
			||||||
 | 
					type MemProvider struct {
 | 
				
			||||||
 | 
						lock        sync.RWMutex
 | 
				
			||||||
 | 
						maxLifetime int64
 | 
				
			||||||
 | 
						data        map[string]*list.Element
 | 
				
			||||||
 | 
						// A priority list whose lastAccess newer gets higher priority.
 | 
				
			||||||
 | 
						list *list.List
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Init initializes memory session provider.
 | 
				
			||||||
 | 
					func (p *MemProvider) Init(maxLifetime int64, _ string) error {
 | 
				
			||||||
 | 
						p.lock.Lock()
 | 
				
			||||||
 | 
						p.maxLifetime = maxLifetime
 | 
				
			||||||
 | 
						p.lock.Unlock()
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// update expands time of session store by given ID.
 | 
				
			||||||
 | 
					func (p *MemProvider) update(sid string) error {
 | 
				
			||||||
 | 
						p.lock.Lock()
 | 
				
			||||||
 | 
						defer p.lock.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if e, ok := p.data[sid]; ok {
 | 
				
			||||||
 | 
							e.Value.(*MemStore).lastAccess = time.Now()
 | 
				
			||||||
 | 
							p.list.MoveToFront(e)
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Read returns raw session store by session ID.
 | 
				
			||||||
 | 
					func (p *MemProvider) Read(sid string) (_ session.RawStore, err error) {
 | 
				
			||||||
 | 
						p.lock.RLock()
 | 
				
			||||||
 | 
						e, ok := p.data[sid]
 | 
				
			||||||
 | 
						p.lock.RUnlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if ok {
 | 
				
			||||||
 | 
							if err = p.update(sid); err != nil {
 | 
				
			||||||
 | 
								return nil, err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return e.Value.(*MemStore), nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Create a new session.
 | 
				
			||||||
 | 
						p.lock.Lock()
 | 
				
			||||||
 | 
						defer p.lock.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s := NewMemStore(sid)
 | 
				
			||||||
 | 
						p.data[sid] = p.list.PushBack(s)
 | 
				
			||||||
 | 
						return s, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Exist returns true if session with given ID exists.
 | 
				
			||||||
 | 
					func (p *MemProvider) Exist(sid string) bool {
 | 
				
			||||||
 | 
						p.lock.RLock()
 | 
				
			||||||
 | 
						defer p.lock.RUnlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						_, ok := p.data[sid]
 | 
				
			||||||
 | 
						return ok
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Destory deletes a session by session ID.
 | 
				
			||||||
 | 
					func (p *MemProvider) Destory(sid string) error {
 | 
				
			||||||
 | 
						p.lock.Lock()
 | 
				
			||||||
 | 
						defer p.lock.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						e, ok := p.data[sid]
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p.list.Remove(e)
 | 
				
			||||||
 | 
						delete(p.data, sid)
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Regenerate regenerates a session store from old session ID to new one.
 | 
				
			||||||
 | 
					func (p *MemProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
 | 
				
			||||||
 | 
						if p.Exist(sid) {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("new sid '%s' already exists", sid)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s, err := p.Read(oldsid)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if err = p.Destory(oldsid); err != nil {
 | 
				
			||||||
 | 
							return nil, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s.(*MemStore).sid = sid
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						p.lock.Lock()
 | 
				
			||||||
 | 
						defer p.lock.Unlock()
 | 
				
			||||||
 | 
						p.data[sid] = p.list.PushBack(s)
 | 
				
			||||||
 | 
						return s, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Count counts and returns number of sessions.
 | 
				
			||||||
 | 
					func (p *MemProvider) Count() int {
 | 
				
			||||||
 | 
						return p.list.Len()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GC calls GC to clean expired sessions.
 | 
				
			||||||
 | 
					func (p *MemProvider) GC() {
 | 
				
			||||||
 | 
						p.lock.RLock()
 | 
				
			||||||
 | 
						for {
 | 
				
			||||||
 | 
							// No session in the list.
 | 
				
			||||||
 | 
							e := p.list.Back()
 | 
				
			||||||
 | 
							if e == nil {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if (e.Value.(*MemStore).lastAccess.Unix() + p.maxLifetime) < time.Now().Unix() {
 | 
				
			||||||
 | 
								p.lock.RUnlock()
 | 
				
			||||||
 | 
								p.lock.Lock()
 | 
				
			||||||
 | 
								p.list.Remove(e)
 | 
				
			||||||
 | 
								delete(p.data, e.Value.(*MemStore).sid)
 | 
				
			||||||
 | 
								p.lock.Unlock()
 | 
				
			||||||
 | 
								p.lock.RLock()
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								break
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						p.lock.RUnlock()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										194
									
								
								modules/session/virtual.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										194
									
								
								modules/session/virtual.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,194 @@
 | 
				
			|||||||
 | 
					// Copyright 2019 The Gitea Authors. All rights reserved.
 | 
				
			||||||
 | 
					// Use of this source code is governed by a MIT-style
 | 
				
			||||||
 | 
					// license that can be found in the LICENSE file.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					package session
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"container/list"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/go-macaron/session"
 | 
				
			||||||
 | 
						couchbase "github.com/go-macaron/session/couchbase"
 | 
				
			||||||
 | 
						memcache "github.com/go-macaron/session/memcache"
 | 
				
			||||||
 | 
						mysql "github.com/go-macaron/session/mysql"
 | 
				
			||||||
 | 
						nodb "github.com/go-macaron/session/nodb"
 | 
				
			||||||
 | 
						postgres "github.com/go-macaron/session/postgres"
 | 
				
			||||||
 | 
						redis "github.com/go-macaron/session/redis"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// VirtualSessionProvider represents a shadowed session provider implementation.
 | 
				
			||||||
 | 
					type VirtualSessionProvider struct {
 | 
				
			||||||
 | 
						lock        sync.RWMutex
 | 
				
			||||||
 | 
						maxlifetime int64
 | 
				
			||||||
 | 
						rootPath    string
 | 
				
			||||||
 | 
						provider    session.Provider
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Init initializes the cookie session provider with given root path.
 | 
				
			||||||
 | 
					func (o *VirtualSessionProvider) Init(gclifetime int64, config string) error {
 | 
				
			||||||
 | 
						var opts session.Options
 | 
				
			||||||
 | 
						if err := json.Unmarshal([]byte(config), &opts); err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						// Note that these options are unprepared so we can't just use NewManager here.
 | 
				
			||||||
 | 
						// Nor can we access the provider map in session.
 | 
				
			||||||
 | 
						// So we will just have to do this by hand.
 | 
				
			||||||
 | 
						// This is only slightly more wrong than modules/setting/session.go:23
 | 
				
			||||||
 | 
						switch opts.Provider {
 | 
				
			||||||
 | 
						case "memory":
 | 
				
			||||||
 | 
							o.provider = &MemProvider{list: list.New(), data: make(map[string]*list.Element)}
 | 
				
			||||||
 | 
						case "file":
 | 
				
			||||||
 | 
							o.provider = &session.FileProvider{}
 | 
				
			||||||
 | 
						case "redis":
 | 
				
			||||||
 | 
							o.provider = &redis.RedisProvider{}
 | 
				
			||||||
 | 
						case "mysql":
 | 
				
			||||||
 | 
							o.provider = &mysql.MysqlProvider{}
 | 
				
			||||||
 | 
						case "postgres":
 | 
				
			||||||
 | 
							o.provider = &postgres.PostgresProvider{}
 | 
				
			||||||
 | 
						case "couchbase":
 | 
				
			||||||
 | 
							o.provider = &couchbase.CouchbaseProvider{}
 | 
				
			||||||
 | 
						case "memcache":
 | 
				
			||||||
 | 
							o.provider = &memcache.MemcacheProvider{}
 | 
				
			||||||
 | 
						case "nodb":
 | 
				
			||||||
 | 
							o.provider = &nodb.NodbProvider{}
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							return fmt.Errorf("VirtualSessionProvider: Unknown Provider: %s", opts.Provider)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return o.provider.Init(gclifetime, opts.ProviderConfig)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Read returns raw session store by session ID.
 | 
				
			||||||
 | 
					func (o *VirtualSessionProvider) Read(sid string) (session.RawStore, error) {
 | 
				
			||||||
 | 
						o.lock.RLock()
 | 
				
			||||||
 | 
						defer o.lock.RUnlock()
 | 
				
			||||||
 | 
						if o.provider.Exist(sid) {
 | 
				
			||||||
 | 
							return o.provider.Read(sid)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						kv := make(map[interface{}]interface{})
 | 
				
			||||||
 | 
						kv["_old_uid"] = "0"
 | 
				
			||||||
 | 
						return NewVirtualStore(o, sid, kv), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Exist returns true if session with given ID exists.
 | 
				
			||||||
 | 
					func (o *VirtualSessionProvider) Exist(sid string) bool {
 | 
				
			||||||
 | 
						return true
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Destory deletes a session by session ID.
 | 
				
			||||||
 | 
					func (o *VirtualSessionProvider) Destory(sid string) error {
 | 
				
			||||||
 | 
						o.lock.Lock()
 | 
				
			||||||
 | 
						defer o.lock.Unlock()
 | 
				
			||||||
 | 
						return o.provider.Destory(sid)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Regenerate regenerates a session store from old session ID to new one.
 | 
				
			||||||
 | 
					func (o *VirtualSessionProvider) Regenerate(oldsid, sid string) (session.RawStore, error) {
 | 
				
			||||||
 | 
						o.lock.Lock()
 | 
				
			||||||
 | 
						defer o.lock.Unlock()
 | 
				
			||||||
 | 
						return o.provider.Regenerate(oldsid, sid)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Count counts and returns number of sessions.
 | 
				
			||||||
 | 
					func (o *VirtualSessionProvider) Count() int {
 | 
				
			||||||
 | 
						o.lock.RLock()
 | 
				
			||||||
 | 
						defer o.lock.RUnlock()
 | 
				
			||||||
 | 
						return o.provider.Count()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GC calls GC to clean expired sessions.
 | 
				
			||||||
 | 
					func (o *VirtualSessionProvider) GC() {
 | 
				
			||||||
 | 
						o.provider.GC()
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						session.Register("VirtualSession", &VirtualSessionProvider{})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// VirtualStore represents a virtual session store implementation.
 | 
				
			||||||
 | 
					type VirtualStore struct {
 | 
				
			||||||
 | 
						p    *VirtualSessionProvider
 | 
				
			||||||
 | 
						sid  string
 | 
				
			||||||
 | 
						lock sync.RWMutex
 | 
				
			||||||
 | 
						data map[interface{}]interface{}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// NewVirtualStore creates and returns a virtual session store.
 | 
				
			||||||
 | 
					func NewVirtualStore(p *VirtualSessionProvider, sid string, kv map[interface{}]interface{}) *VirtualStore {
 | 
				
			||||||
 | 
						return &VirtualStore{
 | 
				
			||||||
 | 
							p:    p,
 | 
				
			||||||
 | 
							sid:  sid,
 | 
				
			||||||
 | 
							data: kv,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Set sets value to given key in session.
 | 
				
			||||||
 | 
					func (s *VirtualStore) Set(key, val interface{}) error {
 | 
				
			||||||
 | 
						s.lock.Lock()
 | 
				
			||||||
 | 
						defer s.lock.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s.data[key] = val
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Get gets value by given key in session.
 | 
				
			||||||
 | 
					func (s *VirtualStore) Get(key interface{}) interface{} {
 | 
				
			||||||
 | 
						s.lock.RLock()
 | 
				
			||||||
 | 
						defer s.lock.RUnlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return s.data[key]
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Delete delete a key from session.
 | 
				
			||||||
 | 
					func (s *VirtualStore) Delete(key interface{}) error {
 | 
				
			||||||
 | 
						s.lock.Lock()
 | 
				
			||||||
 | 
						defer s.lock.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						delete(s.data, key)
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ID returns current session ID.
 | 
				
			||||||
 | 
					func (s *VirtualStore) ID() string {
 | 
				
			||||||
 | 
						return s.sid
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Release releases resource and save data to provider.
 | 
				
			||||||
 | 
					func (s *VirtualStore) Release() error {
 | 
				
			||||||
 | 
						s.lock.Lock()
 | 
				
			||||||
 | 
						defer s.lock.Unlock()
 | 
				
			||||||
 | 
						// Now need to lock the provider
 | 
				
			||||||
 | 
						s.p.lock.Lock()
 | 
				
			||||||
 | 
						defer s.p.lock.Unlock()
 | 
				
			||||||
 | 
						if oldUID, ok := s.data["_old_uid"]; (ok && (oldUID != "0" || len(s.data) > 1)) || (!ok && len(s.data) > 0) {
 | 
				
			||||||
 | 
							// Now ensure that we don't exist!
 | 
				
			||||||
 | 
							realProvider := s.p.provider
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if realProvider.Exist(s.sid) {
 | 
				
			||||||
 | 
								// This is an error!
 | 
				
			||||||
 | 
								return fmt.Errorf("new sid '%s' already exists", s.sid)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							realStore, err := realProvider.Read(s.sid)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							for key, value := range s.data {
 | 
				
			||||||
 | 
								if err := realStore.Set(key, value); err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return realStore.Release()
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Flush deletes all session data.
 | 
				
			||||||
 | 
					func (s *VirtualStore) Flush() error {
 | 
				
			||||||
 | 
						s.lock.Lock()
 | 
				
			||||||
 | 
						defer s.lock.Unlock()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s.data = make(map[interface{}]interface{})
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -5,11 +5,15 @@
 | 
				
			|||||||
package setting
 | 
					package setting
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
	"path"
 | 
						"path"
 | 
				
			||||||
	"path/filepath"
 | 
						"path/filepath"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
 | 
						// This ensures that VirtualSessionProvider is available
 | 
				
			||||||
 | 
						_ "code.gitea.io/gitea/modules/session"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/go-macaron/session"
 | 
						"github.com/go-macaron/session"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -31,5 +35,12 @@ func newSessionService() {
 | 
				
			|||||||
	SessionConfig.Gclifetime = Cfg.Section("session").Key("GC_INTERVAL_TIME").MustInt64(86400)
 | 
						SessionConfig.Gclifetime = Cfg.Section("session").Key("GC_INTERVAL_TIME").MustInt64(86400)
 | 
				
			||||||
	SessionConfig.Maxlifetime = Cfg.Section("session").Key("SESSION_LIFE_TIME").MustInt64(86400)
 | 
						SessionConfig.Maxlifetime = Cfg.Section("session").Key("SESSION_LIFE_TIME").MustInt64(86400)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						shadowConfig, err := json.Marshal(SessionConfig)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							log.Fatal(4, "Can't shadow session config: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						SessionConfig.ProviderConfig = string(shadowConfig)
 | 
				
			||||||
 | 
						SessionConfig.Provider = "VirtualSession"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	log.Info("Session Service Enabled")
 | 
						log.Info("Session Service Enabled")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -299,12 +299,14 @@ var (
 | 
				
			|||||||
		Enable                     bool
 | 
							Enable                     bool
 | 
				
			||||||
		AccessTokenExpirationTime  int64
 | 
							AccessTokenExpirationTime  int64
 | 
				
			||||||
		RefreshTokenExpirationTime int64
 | 
							RefreshTokenExpirationTime int64
 | 
				
			||||||
 | 
							InvalidateRefreshTokens    bool
 | 
				
			||||||
		JWTSecretBytes             []byte `ini:"-"`
 | 
							JWTSecretBytes             []byte `ini:"-"`
 | 
				
			||||||
		JWTSecretBase64            string `ini:"JWT_SECRET"`
 | 
							JWTSecretBase64            string `ini:"JWT_SECRET"`
 | 
				
			||||||
	}{
 | 
						}{
 | 
				
			||||||
		Enable:                     true,
 | 
							Enable:                     true,
 | 
				
			||||||
		AccessTokenExpirationTime:  3600,
 | 
							AccessTokenExpirationTime:  3600,
 | 
				
			||||||
		RefreshTokenExpirationTime: 730,
 | 
							RefreshTokenExpirationTime: 730,
 | 
				
			||||||
 | 
							InvalidateRefreshTokens:    false,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	U2F = struct {
 | 
						U2F = struct {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,7 +19,9 @@ const (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
	// GitRefNamePattern is regular expression with unallowed characters in git reference name
 | 
						// GitRefNamePattern is regular expression with unallowed characters in git reference name
 | 
				
			||||||
	GitRefNamePattern = regexp.MustCompile("[^\\d\\w-_\\./]")
 | 
						// They cannot have ASCII control characters (i.e. bytes whose values are lower than \040, or \177 DEL), space, tilde ~, caret ^, or colon : anywhere.
 | 
				
			||||||
 | 
						// They cannot have question-mark ?, asterisk *, or open bracket [ anywhere
 | 
				
			||||||
 | 
						GitRefNamePattern = regexp.MustCompile(`[\000-\037\177 \\~^:?*[]+`)
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// AddBindingRules adds additional binding rules
 | 
					// AddBindingRules adds additional binding rules
 | 
				
			||||||
@@ -44,7 +46,8 @@ func addGitRefNameBindingRule() {
 | 
				
			|||||||
			// Additional rules as described at https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
 | 
								// Additional rules as described at https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html
 | 
				
			||||||
			if strings.HasPrefix(str, "/") || strings.HasSuffix(str, "/") ||
 | 
								if strings.HasPrefix(str, "/") || strings.HasSuffix(str, "/") ||
 | 
				
			||||||
				strings.HasSuffix(str, ".") || strings.Contains(str, "..") ||
 | 
									strings.HasSuffix(str, ".") || strings.Contains(str, "..") ||
 | 
				
			||||||
				strings.Contains(str, "//") {
 | 
									strings.Contains(str, "//") || strings.Contains(str, "@{") ||
 | 
				
			||||||
 | 
									str == "@" {
 | 
				
			||||||
				errs.Add([]string{name}, ErrGitRefName, "GitRefName")
 | 
									errs.Add([]string{name}, ErrGitRefName, "GitRefName")
 | 
				
			||||||
				return false, errs
 | 
									return false, errs
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -25,6 +25,13 @@ var gitRefNameValidationTestCases = []validationTestCase{
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
		expectedErrors: binding.Errors{},
 | 
							expectedErrors: binding.Errors{},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							description: "Reference name has allowed special characters",
 | 
				
			||||||
 | 
							data: TestForm{
 | 
				
			||||||
 | 
								BranchName: "debian/1%1.6.0-2",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							expectedErrors: binding.Errors{},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
	{
 | 
						{
 | 
				
			||||||
		description: "Reference name contains backslash",
 | 
							description: "Reference name contains backslash",
 | 
				
			||||||
		data: TestForm{
 | 
							data: TestForm{
 | 
				
			||||||
@@ -129,6 +136,123 @@ var gitRefNameValidationTestCases = []validationTestCase{
 | 
				
			|||||||
			},
 | 
								},
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							description: "Reference name is single @",
 | 
				
			||||||
 | 
							data: TestForm{
 | 
				
			||||||
 | 
								BranchName: "@",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							expectedErrors: binding.Errors{
 | 
				
			||||||
 | 
								binding.Error{
 | 
				
			||||||
 | 
									FieldNames:     []string{"BranchName"},
 | 
				
			||||||
 | 
									Classification: ErrGitRefName,
 | 
				
			||||||
 | 
									Message:        "GitRefName",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							description: "Reference name has @{",
 | 
				
			||||||
 | 
							data: TestForm{
 | 
				
			||||||
 | 
								BranchName: "branch@{",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							expectedErrors: binding.Errors{
 | 
				
			||||||
 | 
								binding.Error{
 | 
				
			||||||
 | 
									FieldNames:     []string{"BranchName"},
 | 
				
			||||||
 | 
									Classification: ErrGitRefName,
 | 
				
			||||||
 | 
									Message:        "GitRefName",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							description: "Reference name has unallowed special character ~",
 | 
				
			||||||
 | 
							data: TestForm{
 | 
				
			||||||
 | 
								BranchName: "~debian/1%1.6.0-2",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							expectedErrors: binding.Errors{
 | 
				
			||||||
 | 
								binding.Error{
 | 
				
			||||||
 | 
									FieldNames:     []string{"BranchName"},
 | 
				
			||||||
 | 
									Classification: ErrGitRefName,
 | 
				
			||||||
 | 
									Message:        "GitRefName",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							description: "Reference name has unallowed special character *",
 | 
				
			||||||
 | 
							data: TestForm{
 | 
				
			||||||
 | 
								BranchName: "*debian/1%1.6.0-2",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							expectedErrors: binding.Errors{
 | 
				
			||||||
 | 
								binding.Error{
 | 
				
			||||||
 | 
									FieldNames:     []string{"BranchName"},
 | 
				
			||||||
 | 
									Classification: ErrGitRefName,
 | 
				
			||||||
 | 
									Message:        "GitRefName",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							description: "Reference name has unallowed special character ?",
 | 
				
			||||||
 | 
							data: TestForm{
 | 
				
			||||||
 | 
								BranchName: "?debian/1%1.6.0-2",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							expectedErrors: binding.Errors{
 | 
				
			||||||
 | 
								binding.Error{
 | 
				
			||||||
 | 
									FieldNames:     []string{"BranchName"},
 | 
				
			||||||
 | 
									Classification: ErrGitRefName,
 | 
				
			||||||
 | 
									Message:        "GitRefName",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							description: "Reference name has unallowed special character ^",
 | 
				
			||||||
 | 
							data: TestForm{
 | 
				
			||||||
 | 
								BranchName: "^debian/1%1.6.0-2",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							expectedErrors: binding.Errors{
 | 
				
			||||||
 | 
								binding.Error{
 | 
				
			||||||
 | 
									FieldNames:     []string{"BranchName"},
 | 
				
			||||||
 | 
									Classification: ErrGitRefName,
 | 
				
			||||||
 | 
									Message:        "GitRefName",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							description: "Reference name has unallowed special character :",
 | 
				
			||||||
 | 
							data: TestForm{
 | 
				
			||||||
 | 
								BranchName: "debian:jessie",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							expectedErrors: binding.Errors{
 | 
				
			||||||
 | 
								binding.Error{
 | 
				
			||||||
 | 
									FieldNames:     []string{"BranchName"},
 | 
				
			||||||
 | 
									Classification: ErrGitRefName,
 | 
				
			||||||
 | 
									Message:        "GitRefName",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							description: "Reference name has unallowed special character (whitespace)",
 | 
				
			||||||
 | 
							data: TestForm{
 | 
				
			||||||
 | 
								BranchName: "debian jessie",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							expectedErrors: binding.Errors{
 | 
				
			||||||
 | 
								binding.Error{
 | 
				
			||||||
 | 
									FieldNames:     []string{"BranchName"},
 | 
				
			||||||
 | 
									Classification: ErrGitRefName,
 | 
				
			||||||
 | 
									Message:        "GitRefName",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						{
 | 
				
			||||||
 | 
							description: "Reference name has unallowed special character [",
 | 
				
			||||||
 | 
							data: TestForm{
 | 
				
			||||||
 | 
								BranchName: "debian[jessie",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							expectedErrors: binding.Errors{
 | 
				
			||||||
 | 
								binding.Error{
 | 
				
			||||||
 | 
									FieldNames:     []string{"BranchName"},
 | 
				
			||||||
 | 
									Classification: ErrGitRefName,
 | 
				
			||||||
 | 
									Message:        "GitRefName",
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func Test_GitRefNameValidation(t *testing.T) {
 | 
					func Test_GitRefNameValidation(t *testing.T) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -566,7 +566,9 @@ mirror_prune_desc = Remove obsolete remote-tracking references
 | 
				
			|||||||
mirror_interval = Mirror Interval (valid time units are 'h', 'm', 's'). 0 to disable automatic sync.
 | 
					mirror_interval = Mirror Interval (valid time units are 'h', 'm', 's'). 0 to disable automatic sync.
 | 
				
			||||||
mirror_interval_invalid = The mirror interval is not valid.
 | 
					mirror_interval_invalid = The mirror interval is not valid.
 | 
				
			||||||
mirror_address = Clone From URL
 | 
					mirror_address = Clone From URL
 | 
				
			||||||
mirror_address_desc = Include any required authorization credentials in the URL.
 | 
					mirror_address_desc = Include any required authorization credentials in the URL. These must be url escaped as appropriate
 | 
				
			||||||
 | 
					mirror_address_url_invalid = The provided url is invalid. You must escape all components of the url correctly.
 | 
				
			||||||
 | 
					mirror_address_protocol_invalid = The provided url is invalid. Only http(s):// or git:// locations can be mirrored from.
 | 
				
			||||||
mirror_last_synced = Last Synchronized
 | 
					mirror_last_synced = Last Synchronized
 | 
				
			||||||
watchers = Watchers
 | 
					watchers = Watchers
 | 
				
			||||||
stargazers = Stargazers
 | 
					stargazers = Stargazers
 | 
				
			||||||
@@ -683,6 +685,7 @@ editor.cannot_commit_to_protected_branch = Cannot commit to protected branch '%s
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
commits.desc = Browse source code change history.
 | 
					commits.desc = Browse source code change history.
 | 
				
			||||||
commits.commits = Commits
 | 
					commits.commits = Commits
 | 
				
			||||||
 | 
					commits.no_commits = No commits in common. '%s' and '%s' have entirely different histories.
 | 
				
			||||||
commits.search = Search commits…
 | 
					commits.search = Search commits…
 | 
				
			||||||
commits.find = Search
 | 
					commits.find = Search
 | 
				
			||||||
commits.search_all = All Branches
 | 
					commits.search_all = All Branches
 | 
				
			||||||
 
 | 
				
			|||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -300,6 +300,10 @@ pre, code {
 | 
				
			|||||||
        font-size: .92857143rem;
 | 
					        font-size: .92857143rem;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    &.menu .ui.dropdown.item .menu .item {
 | 
				
			||||||
 | 
					        margin-right: auto;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    &.dropdown .menu>.item>.floating.label {
 | 
					    &.dropdown .menu>.item>.floating.label {
 | 
				
			||||||
        z-index: 11;
 | 
					        z-index: 11;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -129,6 +129,7 @@
 | 
				
			|||||||
            .dropdown {
 | 
					            .dropdown {
 | 
				
			||||||
                .dropdown.icon {
 | 
					                .dropdown.icon {
 | 
				
			||||||
                    margin-top: -7px!important;
 | 
					                    margin-top: -7px!important;
 | 
				
			||||||
 | 
					                    padding-bottom: 5px;
 | 
				
			||||||
                }
 | 
					                }
 | 
				
			||||||
                .text {
 | 
					                .text {
 | 
				
			||||||
                    margin-right: 0!important;
 | 
					                    margin-right: 0!important;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/context"
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/routers/api/v1/convert"
 | 
				
			||||||
	"code.gitea.io/gitea/routers/api/v1/user"
 | 
						"code.gitea.io/gitea/routers/api/v1/user"
 | 
				
			||||||
	api "code.gitea.io/sdk/gitea"
 | 
						api "code.gitea.io/sdk/gitea"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -319,8 +320,14 @@ func GetAllUsers(ctx *context.APIContext) {
 | 
				
			|||||||
		PageSize: -1,
 | 
							PageSize: -1,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		ctx.Error(500, "SearchUsers", err)
 | 
							ctx.Error(500, "GetAllUsers", err)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ctx.JSON(200, &users)
 | 
					
 | 
				
			||||||
 | 
						results := make([]*api.User, len(users))
 | 
				
			||||||
 | 
						for i := range users {
 | 
				
			||||||
 | 
							results[i] = convert.ToUser(users[i], ctx.IsSigned, ctx.User.IsAdmin)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ctx.JSON(200, &results)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -172,6 +172,10 @@ func reqToken() macaron.Handler {
 | 
				
			|||||||
		if true == ctx.Data["IsApiToken"] {
 | 
							if true == ctx.Data["IsApiToken"] {
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							if ctx.Context.IsBasicAuth {
 | 
				
			||||||
 | 
								ctx.CheckForOTP()
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
		if ctx.IsSigned {
 | 
							if ctx.IsSigned {
 | 
				
			||||||
			ctx.RequireCSRF()
 | 
								ctx.RequireCSRF()
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
@@ -181,11 +185,12 @@ func reqToken() macaron.Handler {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func reqBasicAuth() macaron.Handler {
 | 
					func reqBasicAuth() macaron.Handler {
 | 
				
			||||||
	return func(ctx *context.Context) {
 | 
						return func(ctx *context.APIContext) {
 | 
				
			||||||
		if !ctx.IsBasicAuth {
 | 
							if !ctx.Context.IsBasicAuth {
 | 
				
			||||||
			ctx.Error(401)
 | 
								ctx.Context.Error(401)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							ctx.CheckForOTP()
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/git"
 | 
						"code.gitea.io/git"
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/markup"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/util"
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -217,3 +218,18 @@ func ToTeam(team *models.Team) *api.Team {
 | 
				
			|||||||
		Units:       team.GetUnitNames(),
 | 
							Units:       team.GetUnitNames(),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// ToUser convert models.User to api.User
 | 
				
			||||||
 | 
					func ToUser(user *models.User, signed, admin bool) *api.User {
 | 
				
			||||||
 | 
						result := &api.User{
 | 
				
			||||||
 | 
							ID:        user.ID,
 | 
				
			||||||
 | 
							UserName:  user.Name,
 | 
				
			||||||
 | 
							AvatarURL: user.AvatarLink(),
 | 
				
			||||||
 | 
							FullName:  markup.Sanitize(user.FullName),
 | 
				
			||||||
 | 
							IsAdmin:   user.IsAdmin,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if signed && (!user.KeepEmailPrivate || admin) {
 | 
				
			||||||
 | 
							result.Email = user.Email
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return result
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,6 +38,11 @@ func ListTeams(ctx *context.APIContext) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	apiTeams := make([]*api.Team, len(org.Teams))
 | 
						apiTeams := make([]*api.Team, len(org.Teams))
 | 
				
			||||||
	for i := range org.Teams {
 | 
						for i := range org.Teams {
 | 
				
			||||||
 | 
							if err := org.Teams[i].GetUnits(); err != nil {
 | 
				
			||||||
 | 
								ctx.Error(500, "GetUnits", err)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		apiTeams[i] = convert.ToTeam(org.Teams[i])
 | 
							apiTeams[i] = convert.ToTeam(org.Teams[i])
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ctx.JSON(200, apiTeams)
 | 
						ctx.JSON(200, apiTeams)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -106,7 +106,7 @@ func GetSingleCommit(ctx *context.APIContext) {
 | 
				
			|||||||
				Email: commit.Committer.Email,
 | 
									Email: commit.Committer.Email,
 | 
				
			||||||
				Date:  commit.Committer.When.Format(time.RFC3339),
 | 
									Date:  commit.Committer.When.Format(time.RFC3339),
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			Message: commit.Summary(),
 | 
								Message: commit.Message(),
 | 
				
			||||||
			Tree: &api.CommitMeta{
 | 
								Tree: &api.CommitMeta{
 | 
				
			||||||
				URL: ctx.Repo.Repository.APIURL() + "/trees/" + commit.ID.String(),
 | 
									URL: ctx.Repo.Repository.APIURL() + "/trees/" + commit.ID.String(),
 | 
				
			||||||
				SHA: commit.ID.String(),
 | 
									SHA: commit.ID.String(),
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -61,14 +61,14 @@ type swaggerResponseReferenceList struct {
 | 
				
			|||||||
// swagger:response Hook
 | 
					// swagger:response Hook
 | 
				
			||||||
type swaggerResponseHook struct {
 | 
					type swaggerResponseHook struct {
 | 
				
			||||||
	// in:body
 | 
						// in:body
 | 
				
			||||||
	Body []api.Branch `json:"body"`
 | 
						Body api.Hook `json:"body"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// HookList
 | 
					// HookList
 | 
				
			||||||
// swagger:response HookList
 | 
					// swagger:response HookList
 | 
				
			||||||
type swaggerResponseHookList struct {
 | 
					type swaggerResponseHookList struct {
 | 
				
			||||||
	// in:body
 | 
						// in:body
 | 
				
			||||||
	Body []api.Branch `json:"body"`
 | 
						Body []api.Hook `json:"body"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Release
 | 
					// Release
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,7 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/context"
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/markup"
 | 
						"code.gitea.io/gitea/routers/api/v1/convert"
 | 
				
			||||||
	api "code.gitea.io/sdk/gitea"
 | 
						api "code.gitea.io/sdk/gitea"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/Unknwon/com"
 | 
						"github.com/Unknwon/com"
 | 
				
			||||||
@@ -55,9 +55,6 @@ func Search(ctx *context.APIContext) {
 | 
				
			|||||||
		Type:     models.UserTypeIndividual,
 | 
							Type:     models.UserTypeIndividual,
 | 
				
			||||||
		PageSize: com.StrTo(ctx.Query("limit")).MustInt(),
 | 
							PageSize: com.StrTo(ctx.Query("limit")).MustInt(),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if opts.PageSize <= 0 {
 | 
					 | 
				
			||||||
		opts.PageSize = 10
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
	users, _, err := models.SearchUsers(opts)
 | 
						users, _, err := models.SearchUsers(opts)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
@@ -70,16 +67,7 @@ func Search(ctx *context.APIContext) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	results := make([]*api.User, len(users))
 | 
						results := make([]*api.User, len(users))
 | 
				
			||||||
	for i := range users {
 | 
						for i := range users {
 | 
				
			||||||
		results[i] = &api.User{
 | 
							results[i] = convert.ToUser(users[i], ctx.IsSigned, ctx.User.IsAdmin)
 | 
				
			||||||
			ID:        users[i].ID,
 | 
					 | 
				
			||||||
			UserName:  users[i].Name,
 | 
					 | 
				
			||||||
			AvatarURL: users[i].AvatarLink(),
 | 
					 | 
				
			||||||
			FullName:  markup.Sanitize(users[i].FullName),
 | 
					 | 
				
			||||||
			IsAdmin:   users[i].IsAdmin,
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if ctx.IsSigned && (!users[i].KeepEmailPrivate || ctx.User.IsAdmin) {
 | 
					 | 
				
			||||||
			results[i].Email = users[i].Email
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.JSON(200, map[string]interface{}{
 | 
						ctx.JSON(200, map[string]interface{}{
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -14,6 +14,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/base"
 | 
						"code.gitea.io/gitea/modules/base"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/context"
 | 
						"code.gitea.io/gitea/modules/context"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/log"
 | 
						"code.gitea.io/gitea/modules/log"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
@@ -250,5 +251,5 @@ func CreateBranch(ctx *context.Context, form auth.NewBranchForm) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.Flash.Success(ctx.Tr("repo.branch.create_success", form.NewBranchName))
 | 
						ctx.Flash.Success(ctx.Tr("repo.branch.create_success", form.NewBranchName))
 | 
				
			||||||
	ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + form.NewBranchName)
 | 
						ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(form.NewBranchName))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -20,6 +20,7 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/setting"
 | 
						"code.gitea.io/gitea/modules/setting"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/templates"
 | 
						"code.gitea.io/gitea/modules/templates"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/uploader"
 | 
						"code.gitea.io/gitea/modules/uploader"
 | 
				
			||||||
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
@@ -66,9 +67,9 @@ func editFile(ctx *context.Context, isNewFile bool) {
 | 
				
			|||||||
	treePath := cleanUploadFileName(ctx.Repo.TreePath)
 | 
						treePath := cleanUploadFileName(ctx.Repo.TreePath)
 | 
				
			||||||
	if treePath != ctx.Repo.TreePath {
 | 
						if treePath != ctx.Repo.TreePath {
 | 
				
			||||||
		if isNewFile {
 | 
							if isNewFile {
 | 
				
			||||||
			ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_new", ctx.Repo.BranchName, treePath))
 | 
								ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_new", util.PathEscapeSegments(ctx.Repo.BranchName), util.PathEscapeSegments(treePath)))
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_edit", ctx.Repo.BranchName, treePath))
 | 
								ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_edit", util.PathEscapeSegments(ctx.Repo.BranchName), util.PathEscapeSegments(treePath)))
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -324,7 +325,7 @@ func editFilePost(ctx *context.Context, form auth.EditRepoFileForm, isNewFile bo
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + branchName + "/" + strings.NewReplacer("%", "%25", "#", "%23", " ", "%20", "?", "%3F").Replace(form.TreePath))
 | 
						ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(branchName) + "/" + util.PathEscapeSegments(form.TreePath))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// EditFilePost response for editing file
 | 
					// EditFilePost response for editing file
 | 
				
			||||||
@@ -376,7 +377,7 @@ func DeleteFile(ctx *context.Context) {
 | 
				
			|||||||
	treePath := cleanUploadFileName(ctx.Repo.TreePath)
 | 
						treePath := cleanUploadFileName(ctx.Repo.TreePath)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if treePath != ctx.Repo.TreePath {
 | 
						if treePath != ctx.Repo.TreePath {
 | 
				
			||||||
		ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_delete", ctx.Repo.BranchName, treePath))
 | 
							ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_delete", util.PathEscapeSegments(ctx.Repo.BranchName), util.PathEscapeSegments(treePath)))
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -460,7 +461,7 @@ func DeleteFilePost(ctx *context.Context, form auth.DeleteRepoFileForm) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.Flash.Success(ctx.Tr("repo.editor.file_delete_success", ctx.Repo.TreePath))
 | 
						ctx.Flash.Success(ctx.Tr("repo.editor.file_delete_success", ctx.Repo.TreePath))
 | 
				
			||||||
	ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + branchName)
 | 
						ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(branchName))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func renderUploadSettings(ctx *context.Context) {
 | 
					func renderUploadSettings(ctx *context.Context) {
 | 
				
			||||||
@@ -477,7 +478,7 @@ func UploadFile(ctx *context.Context) {
 | 
				
			|||||||
	canCommit := renderCommitRights(ctx)
 | 
						canCommit := renderCommitRights(ctx)
 | 
				
			||||||
	treePath := cleanUploadFileName(ctx.Repo.TreePath)
 | 
						treePath := cleanUploadFileName(ctx.Repo.TreePath)
 | 
				
			||||||
	if treePath != ctx.Repo.TreePath {
 | 
						if treePath != ctx.Repo.TreePath {
 | 
				
			||||||
		ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_upload", ctx.Repo.BranchName, treePath))
 | 
							ctx.Redirect(path.Join(ctx.Repo.RepoLink, "_upload", util.PathEscapeSegments(ctx.Repo.BranchName), util.PathEscapeSegments(treePath)))
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ctx.Repo.TreePath = treePath
 | 
						ctx.Repo.TreePath = treePath
 | 
				
			||||||
@@ -596,7 +597,7 @@ func UploadFilePost(ctx *context.Context, form auth.UploadRepoFileForm) {
 | 
				
			|||||||
		return
 | 
							return
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + branchName + "/" + form.TreePath)
 | 
						ctx.Redirect(ctx.Repo.RepoLink + "/src/branch/" + util.PathEscapeSegments(branchName) + "/" + util.PathEscapeSegments(form.TreePath))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func cleanUploadFileName(name string) string {
 | 
					func cleanUploadFileName(name string) string {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,11 +7,12 @@ package repo
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
 | 
						"net/url"
 | 
				
			||||||
 | 
						"regexp"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"time"
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/git"
 | 
						"code.gitea.io/git"
 | 
				
			||||||
 | 
					 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/auth"
 | 
						"code.gitea.io/gitea/modules/auth"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/base"
 | 
						"code.gitea.io/gitea/modules/base"
 | 
				
			||||||
@@ -21,6 +22,8 @@ import (
 | 
				
			|||||||
	"code.gitea.io/gitea/modules/util"
 | 
						"code.gitea.io/gitea/modules/util"
 | 
				
			||||||
	"code.gitea.io/gitea/modules/validation"
 | 
						"code.gitea.io/gitea/modules/validation"
 | 
				
			||||||
	"code.gitea.io/gitea/routers/utils"
 | 
						"code.gitea.io/gitea/routers/utils"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/mvdan/xurls"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
@@ -33,6 +36,8 @@ const (
 | 
				
			|||||||
	tplProtectedBranch base.TplName = "repo/settings/protected_branch"
 | 
						tplProtectedBranch base.TplName = "repo/settings/protected_branch"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var validFormAddress *regexp.Regexp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// Settings show a repository's settings page
 | 
					// Settings show a repository's settings page
 | 
				
			||||||
func Settings(ctx *context.Context) {
 | 
					func Settings(ctx *context.Context) {
 | 
				
			||||||
	ctx.Data["Title"] = ctx.Tr("repo.settings")
 | 
						ctx.Data["Title"] = ctx.Tr("repo.settings")
 | 
				
			||||||
@@ -124,8 +129,13 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
 | 
				
			|||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// This section doesn't require repo_name/RepoName to be set in the form, don't show it
 | 
				
			||||||
 | 
							// as an error on the UI for this action
 | 
				
			||||||
 | 
							ctx.Data["Err_RepoName"] = nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		interval, err := time.ParseDuration(form.Interval)
 | 
							interval, err := time.ParseDuration(form.Interval)
 | 
				
			||||||
		if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) {
 | 
							if err != nil || (interval != 0 && interval < setting.Mirror.MinInterval) {
 | 
				
			||||||
 | 
								ctx.Data["Err_Interval"] = true
 | 
				
			||||||
			ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form)
 | 
								ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form)
 | 
				
			||||||
		} else {
 | 
							} else {
 | 
				
			||||||
			ctx.Repo.Mirror.EnablePrune = form.EnablePrune
 | 
								ctx.Repo.Mirror.EnablePrune = form.EnablePrune
 | 
				
			||||||
@@ -136,11 +146,43 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
 | 
				
			|||||||
				ctx.Repo.Mirror.NextUpdateUnix = 0
 | 
									ctx.Repo.Mirror.NextUpdateUnix = 0
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			if err := models.UpdateMirror(ctx.Repo.Mirror); err != nil {
 | 
								if err := models.UpdateMirror(ctx.Repo.Mirror); err != nil {
 | 
				
			||||||
 | 
									ctx.Data["Err_Interval"] = true
 | 
				
			||||||
				ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form)
 | 
									ctx.RenderWithErr(ctx.Tr("repo.mirror_interval_invalid"), tplSettingsOptions, &form)
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if err := ctx.Repo.Mirror.SaveAddress(form.MirrorAddress); err != nil {
 | 
					
 | 
				
			||||||
 | 
							// Validate the form.MirrorAddress
 | 
				
			||||||
 | 
							u, err := url.Parse(form.MirrorAddress)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								ctx.Data["Err_MirrorAddress"] = true
 | 
				
			||||||
 | 
								ctx.RenderWithErr(ctx.Tr("repo.mirror_address_url_invalid"), tplSettingsOptions, &form)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if u.Opaque != "" || !(u.Scheme == "http" || u.Scheme == "https" || u.Scheme == "git") {
 | 
				
			||||||
 | 
								ctx.Data["Err_MirrorAddress"] = true
 | 
				
			||||||
 | 
								ctx.RenderWithErr(ctx.Tr("repo.mirror_address_protocol_invalid"), tplSettingsOptions, &form)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Now use xurls
 | 
				
			||||||
 | 
							address := validFormAddress.FindString(form.MirrorAddress)
 | 
				
			||||||
 | 
							if address != form.MirrorAddress && form.MirrorAddress != "" {
 | 
				
			||||||
 | 
								ctx.Data["Err_MirrorAddress"] = true
 | 
				
			||||||
 | 
								ctx.RenderWithErr(ctx.Tr("repo.mirror_address_url_invalid"), tplSettingsOptions, &form)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if u.EscapedPath() == "" || u.Host == "" || !u.IsAbs() {
 | 
				
			||||||
 | 
								ctx.Data["Err_MirrorAddress"] = true
 | 
				
			||||||
 | 
								ctx.RenderWithErr(ctx.Tr("repo.mirror_address_url_invalid"), tplSettingsOptions, &form)
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							address = u.String()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							if err := ctx.Repo.Mirror.SaveAddress(address); err != nil {
 | 
				
			||||||
			ctx.ServerError("SaveAddress", err)
 | 
								ctx.ServerError("SaveAddress", err)
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
@@ -161,6 +203,10 @@ func SettingsPost(ctx *context.Context, form auth.RepoSettingForm) {
 | 
				
			|||||||
	case "advanced":
 | 
						case "advanced":
 | 
				
			||||||
		var units []models.RepoUnit
 | 
							var units []models.RepoUnit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// This section doesn't require repo_name/RepoName to be set in the form, don't show it
 | 
				
			||||||
 | 
							// as an error on the UI for this action
 | 
				
			||||||
 | 
							ctx.Data["Err_RepoName"] = nil
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for _, tp := range models.MustRepoUnits {
 | 
							for _, tp := range models.MustRepoUnits {
 | 
				
			||||||
			units = append(units, models.RepoUnit{
 | 
								units = append(units, models.RepoUnit{
 | 
				
			||||||
				RepoID: repo.ID,
 | 
									RepoID: repo.ID,
 | 
				
			||||||
@@ -673,3 +719,11 @@ func DeleteDeployKey(ctx *context.Context) {
 | 
				
			|||||||
		"redirect": ctx.Repo.RepoLink + "/settings/keys",
 | 
							"redirect": ctx.Repo.RepoLink + "/settings/keys",
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						var err error
 | 
				
			||||||
 | 
						validFormAddress, err = xurls.StrictMatchingScheme(`(https?)|(git)://`)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							panic(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -339,7 +339,7 @@ func RegisterRoutes(m *macaron.Macaron) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	m.Group("/user", func() {
 | 
						m.Group("/user", func() {
 | 
				
			||||||
		// r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds)
 | 
							// r.Get("/feeds", binding.Bind(auth.FeedsForm{}), user.Feeds)
 | 
				
			||||||
		m.Any("/activate", user.Activate)
 | 
							m.Any("/activate", user.Activate, reqSignIn)
 | 
				
			||||||
		m.Any("/activate_email", user.ActivateEmail)
 | 
							m.Any("/activate_email", user.ActivateEmail)
 | 
				
			||||||
		m.Get("/email2user", user.Email2User)
 | 
							m.Get("/email2user", user.Email2User)
 | 
				
			||||||
		m.Get("/forgot_password", user.ForgotPasswd)
 | 
							m.Get("/forgot_password", user.ForgotPasswd)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -9,7 +9,6 @@ import (
 | 
				
			|||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
	"net/url"
 | 
					 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/gitea/models"
 | 
						"code.gitea.io/gitea/models"
 | 
				
			||||||
@@ -96,7 +95,7 @@ func checkAutoLogin(ctx *context.Context) bool {
 | 
				
			|||||||
	if len(redirectTo) > 0 {
 | 
						if len(redirectTo) > 0 {
 | 
				
			||||||
		ctx.SetCookie("redirect_to", redirectTo, 0, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
 | 
							ctx.SetCookie("redirect_to", redirectTo, 0, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		redirectTo, _ = url.QueryUnescape(ctx.GetCookie("redirect_to"))
 | 
							redirectTo = ctx.GetCookie("redirect_to")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if isSucceed {
 | 
						if isSucceed {
 | 
				
			||||||
@@ -496,7 +495,7 @@ func handleSignInFull(ctx *context.Context, u *models.User, remember bool, obeyR
 | 
				
			|||||||
		return setting.AppSubURL + "/"
 | 
							return setting.AppSubURL + "/"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 && !util.IsExternalURL(redirectTo) {
 | 
						if redirectTo := ctx.GetCookie("redirect_to"); len(redirectTo) > 0 && !util.IsExternalURL(redirectTo) {
 | 
				
			||||||
		ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
 | 
							ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
 | 
				
			||||||
		if obeyRedirect {
 | 
							if obeyRedirect {
 | 
				
			||||||
			ctx.RedirectToFirst(redirectTo)
 | 
								ctx.RedirectToFirst(redirectTo)
 | 
				
			||||||
@@ -587,7 +586,7 @@ func handleOAuth2SignIn(u *models.User, gothUser goth.User, ctx *context.Context
 | 
				
			|||||||
				return
 | 
									return
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 {
 | 
								if redirectTo := ctx.GetCookie("redirect_to"); len(redirectTo) > 0 {
 | 
				
			||||||
				ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
 | 
									ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
 | 
				
			||||||
				ctx.RedirectToFirst(redirectTo)
 | 
									ctx.RedirectToFirst(redirectTo)
 | 
				
			||||||
				return
 | 
									return
 | 
				
			||||||
@@ -1298,7 +1297,7 @@ func MustChangePasswordPost(ctx *context.Context, cpt *captcha.Captcha, form aut
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	log.Trace("User updated password: %s", u.Name)
 | 
						log.Trace("User updated password: %s", u.Name)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if redirectTo, _ := url.QueryUnescape(ctx.GetCookie("redirect_to")); len(redirectTo) > 0 && !util.IsExternalURL(redirectTo) {
 | 
						if redirectTo := ctx.GetCookie("redirect_to"); len(redirectTo) > 0 && !util.IsExternalURL(redirectTo) {
 | 
				
			||||||
		ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL)
 | 
							ctx.SetCookie("redirect_to", "", -1, setting.AppSubURL)
 | 
				
			||||||
		ctx.RedirectToFirst(redirectTo)
 | 
							ctx.RedirectToFirst(redirectTo)
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -47,7 +47,7 @@ func SignInOpenID(ctx *context.Context) {
 | 
				
			|||||||
	if len(redirectTo) > 0 {
 | 
						if len(redirectTo) > 0 {
 | 
				
			||||||
		ctx.SetCookie("redirect_to", redirectTo, 0, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
 | 
							ctx.SetCookie("redirect_to", redirectTo, 0, setting.AppSubURL, "", setting.SessionConfig.Secure, true)
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		redirectTo, _ = url.QueryUnescape(ctx.GetCookie("redirect_to"))
 | 
							redirectTo = ctx.GetCookie("redirect_to")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if isSucceed {
 | 
						if isSucceed {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -102,18 +102,19 @@ const (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// AccessTokenResponse represents a successful access token response
 | 
					// AccessTokenResponse represents a successful access token response
 | 
				
			||||||
type AccessTokenResponse struct {
 | 
					type AccessTokenResponse struct {
 | 
				
			||||||
	AccessToken string    `json:"access_token"`
 | 
						AccessToken  string    `json:"access_token"`
 | 
				
			||||||
	TokenType   TokenType `json:"token_type"`
 | 
						TokenType    TokenType `json:"token_type"`
 | 
				
			||||||
	ExpiresIn   int64     `json:"expires_in"`
 | 
						ExpiresIn    int64     `json:"expires_in"`
 | 
				
			||||||
	// TODO implement RefreshToken
 | 
						RefreshToken string    `json:"refresh_token"`
 | 
				
			||||||
	RefreshToken string `json:"refresh_token"`
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func newAccessTokenResponse(grant *models.OAuth2Grant) (*AccessTokenResponse, *AccessTokenError) {
 | 
					func newAccessTokenResponse(grant *models.OAuth2Grant) (*AccessTokenResponse, *AccessTokenError) {
 | 
				
			||||||
	if err := grant.IncreaseCounter(); err != nil {
 | 
						if setting.OAuth2.InvalidateRefreshTokens {
 | 
				
			||||||
		return nil, &AccessTokenError{
 | 
							if err := grant.IncreaseCounter(); err != nil {
 | 
				
			||||||
			ErrorCode:        AccessTokenErrorCodeInvalidGrant,
 | 
								return nil, &AccessTokenError{
 | 
				
			||||||
			ErrorDescription: "cannot increase the grant counter",
 | 
									ErrorCode:        AccessTokenErrorCodeInvalidGrant,
 | 
				
			||||||
 | 
									ErrorDescription: "cannot increase the grant counter",
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// generate access token to access the API
 | 
						// generate access token to access the API
 | 
				
			||||||
@@ -366,7 +367,7 @@ func handleRefreshToken(ctx *context.Context, form auth.AccessTokenForm) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// check if token got already used
 | 
						// check if token got already used
 | 
				
			||||||
	if grant.Counter != token.Counter || token.Counter == 0 {
 | 
						if setting.OAuth2.InvalidateRefreshTokens && (grant.Counter != token.Counter || token.Counter == 0) {
 | 
				
			||||||
		handleAccessTokenError(ctx, AccessTokenError{
 | 
							handleAccessTokenError(ctx, AccessTokenError{
 | 
				
			||||||
			ErrorCode:        AccessTokenErrorCodeUnauthorizedClient,
 | 
								ErrorCode:        AccessTokenErrorCodeUnauthorizedClient,
 | 
				
			||||||
			ErrorDescription: "token was already used",
 | 
								ErrorDescription: "token was already used",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,7 +5,7 @@
 | 
				
			|||||||
	<meta name="viewport" content="width=device-width, initial-scale=1">
 | 
						<meta name="viewport" content="width=device-width, initial-scale=1">
 | 
				
			||||||
	<meta http-equiv="x-ua-compatible" content="ie=edge">
 | 
						<meta http-equiv="x-ua-compatible" content="ie=edge">
 | 
				
			||||||
	<title>{{if .Title}}{{.Title}} - {{end}}{{AppName}}</title>
 | 
						<title>{{if .Title}}{{.Title}} - {{end}}{{AppName}}</title>
 | 
				
			||||||
	<link rel="manifest" href="{{AppSubUrl}}/manifest.json">
 | 
						<link rel="manifest" href="{{AppSubUrl}}/manifest.json" crossorigin="use-credentials">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	<script>
 | 
						<script>
 | 
				
			||||||
		if ('serviceWorker' in navigator) {
 | 
							if ('serviceWorker' in navigator) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -15,28 +15,27 @@
 | 
				
			|||||||
						<span class="help">{{.i18n.Tr "org.org_name_helper"}}</span>
 | 
											<span class="help">{{.i18n.Tr "org.org_name_helper"}}</span>
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
					<div class="inline required field {{if .Err_OrgVisibility}}error{{end}}">
 | 
										<div class="inline field {{if .Err_OrgVisibility}}error{{end}}">
 | 
				
			||||||
						<label for="visibility">{{.i18n.Tr "org.settings.visibility"}}</label>
 | 
											<span class="inline required field"><label for="visibility">{{.i18n.Tr "org.settings.visibility"}}</label></span>
 | 
				
			||||||
						<div class="field">
 | 
											<div class="ui radio checkbox">
 | 
				
			||||||
							<div class="ui radio checkbox">
 | 
												<input class="hidden enable-system-radio" tabindex="0" name="visibility" type="radio" value="0" {{if .DefaultOrgVisibilityMode.IsPublic}}checked{{end}}/>
 | 
				
			||||||
								<input class="hidden enable-system-radio" tabindex="0" name="visibility" type="radio" value="0" {{if .DefaultOrgVisibilityMode.IsPublic}}checked{{end}}/>
 | 
												<label>{{.i18n.Tr "org.settings.visibility.public"}}</label>
 | 
				
			||||||
								<label>{{.i18n.Tr "org.settings.visibility.public"}}</label>
 | 
											</div>
 | 
				
			||||||
							</div>
 | 
										</div>
 | 
				
			||||||
						</div>
 | 
										<div class="inline field {{if .Err_OrgVisibility}}error{{end}}">
 | 
				
			||||||
						<div class="field">
 | 
											<label> </label>
 | 
				
			||||||
							<div class="ui radio checkbox">
 | 
											<div class="ui radio checkbox">
 | 
				
			||||||
								<input class="hidden enable-system-radio" tabindex="0" name="visibility" type="radio" value="1" {{if .DefaultOrgVisibilityMode.IsLimited}}checked{{end}}/>
 | 
												<input class="hidden enable-system-radio" tabindex="0" name="visibility" type="radio" value="1" {{if .DefaultOrgVisibilityMode.IsLimited}}checked{{end}}/>
 | 
				
			||||||
								<label>{{.i18n.Tr "org.settings.visibility.limited"}}</label>
 | 
												<label>{{.i18n.Tr "org.settings.visibility.limited"}}</label>
 | 
				
			||||||
							</div>
 | 
											</div>
 | 
				
			||||||
						</div>
 | 
										</div>
 | 
				
			||||||
						<div class="field">
 | 
										<div class="inline field {{if .Err_OrgVisibility}}error{{end}}">
 | 
				
			||||||
							<div class="ui radio checkbox">
 | 
											<label> </label>
 | 
				
			||||||
								<input class="hidden enable-system-radio" tabindex="0" name="visibility" type="radio" value="2" {{if .DefaultOrgVisibilityMode.IsPrivate}}checked{{end}}/>
 | 
											<div class="ui radio checkbox">
 | 
				
			||||||
								<label>{{.i18n.Tr "org.settings.visibility.private"}}</label>
 | 
												<input class="hidden enable-system-radio" tabindex="0" name="visibility" type="radio" value="2" {{if .DefaultOrgVisibilityMode.IsPrivate}}checked{{end}}/>
 | 
				
			||||||
							</div>
 | 
												<label>{{.i18n.Tr "org.settings.visibility.private"}}</label>
 | 
				
			||||||
						</div>
 | 
											</div>
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
 | 
					 | 
				
			||||||
					<div class="inline field">
 | 
										<div class="inline field">
 | 
				
			||||||
						<label></label>
 | 
											<label></label>
 | 
				
			||||||
						<button class="ui green button">
 | 
											<button class="ui green button">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,13 @@
 | 
				
			|||||||
<h4 class="ui top attached header">
 | 
					<h4 class="ui top attached header">
 | 
				
			||||||
	<div class="ui stackable grid">
 | 
						<div class="ui stackable grid">
 | 
				
			||||||
		<div class="six wide column">
 | 
							<div class="ten wide column">
 | 
				
			||||||
			{{.CommitCount}} {{.i18n.Tr "repo.commits.commits"}} {{if .Branch}}({{.Branch}}){{end}}
 | 
								{{if or .PageIsCommits (gt .CommitCount 0)}}
 | 
				
			||||||
 | 
									{{.CommitCount}} {{.i18n.Tr "repo.commits.commits"}} {{if .Branch}}({{.Branch}}){{end}}
 | 
				
			||||||
 | 
								{{else}}
 | 
				
			||||||
 | 
									{{.i18n.Tr "repo.commits.no_commits" $.BaseBranch $.HeadBranch }} {{if .Branch}}({{.Branch}}){{end}}
 | 
				
			||||||
 | 
								{{end}}
 | 
				
			||||||
		</div>
 | 
							</div>
 | 
				
			||||||
		<div class="ten wide right aligned column">
 | 
							<div class="six wide right aligned column">
 | 
				
			||||||
			{{if .PageIsCommits}}
 | 
								{{if .PageIsCommits}}
 | 
				
			||||||
				<form class="ignore-dirty" action="{{.RepoLink}}/commits/{{.BranchNameSubURL | EscapePound}}/search">
 | 
									<form class="ignore-dirty" action="{{.RepoLink}}/commits/{{.BranchNameSubURL | EscapePound}}/search">
 | 
				
			||||||
					<div class="ui tiny search input">
 | 
										<div class="ui tiny search input">
 | 
				
			||||||
@@ -23,7 +27,7 @@
 | 
				
			|||||||
	</div>
 | 
						</div>
 | 
				
			||||||
</h4>
 | 
					</h4>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
{{if .Commits}}
 | 
					{{if and .Commits (gt .CommitCount 0)}}
 | 
				
			||||||
	<div class="ui attached table segment">
 | 
						<div class="ui attached table segment">
 | 
				
			||||||
		<table class="ui very basic striped fixed table single line" id="commits-table">
 | 
							<table class="ui very basic striped fixed table single line" id="commits-table">
 | 
				
			||||||
			<thead>
 | 
								<thead>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
<form class="ui comment form stackable grid" action="{{EscapePound .Link}}" method="post">
 | 
					<form class="ui comment form stackable grid" action="{{.Link}}" method="post">
 | 
				
			||||||
	{{.CsrfTokenHtml}}
 | 
						{{.CsrfTokenHtml}}
 | 
				
			||||||
	{{if .Flash}}
 | 
						{{if .Flash}}
 | 
				
			||||||
		<div class="sixteen wide column">
 | 
							<div class="sixteen wide column">
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -54,6 +54,9 @@
 | 
				
			|||||||
				<div class="ui segment">
 | 
									<div class="ui segment">
 | 
				
			||||||
					{{.i18n.Tr "repo.pulls.has_pull_request" $.RepoLink $.RepoRelPath .PullRequest.Index | Safe}}
 | 
										{{.i18n.Tr "repo.pulls.has_pull_request" $.RepoLink $.RepoRelPath .PullRequest.Index | Safe}}
 | 
				
			||||||
				</div>
 | 
									</div>
 | 
				
			||||||
 | 
								{{else if eq .CommitCount 0 }}
 | 
				
			||||||
 | 
									{{template "repo/commits_table" .}}
 | 
				
			||||||
 | 
									{{template "repo/diff/box" .}}
 | 
				
			||||||
			{{else}}
 | 
								{{else}}
 | 
				
			||||||
				{{template "repo/issue/new_form" .}}
 | 
									{{template "repo/issue/new_form" .}}
 | 
				
			||||||
				{{template "repo/commits_table" .}}
 | 
									{{template "repo/commits_table" .}}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -77,7 +77,7 @@
 | 
				
			|||||||
									{{if .Attachments}}
 | 
														{{if .Attachments}}
 | 
				
			||||||
										{{range $attachment := .Attachments}}
 | 
															{{range $attachment := .Attachments}}
 | 
				
			||||||
										<li>
 | 
															<li>
 | 
				
			||||||
											<a target="_blank" rel="noopener noreferrer" href="{{$.RepoLink}}/releases/download/{{$release.TagName}}/{{$attachment.Name}}">
 | 
																<a target="_blank" rel="noopener noreferrer" href="{{$.RepoLink}}/releases/download/{{$release.TagName | PathEscape}}/{{$attachment.Name | PathEscape}}">
 | 
				
			||||||
												<strong><span class="ui image octicon octicon-package" title='{{$attachment.Name}}'></span> {{$attachment.Name}}</strong>
 | 
																	<strong><span class="ui image octicon octicon-package" title='{{$attachment.Name}}'></span> {{$attachment.Name}}</strong>
 | 
				
			||||||
												<span class="ui text grey right">{{$attachment.Size | FileSize}}</span>
 | 
																	<span class="ui text grey right">{{$attachment.Size | FileSize}}</span>
 | 
				
			||||||
											</a>
 | 
																</a>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -58,7 +58,7 @@
 | 
				
			|||||||
						<label for="interval">{{.i18n.Tr "repo.mirror_interval"}}</label>
 | 
											<label for="interval">{{.i18n.Tr "repo.mirror_interval"}}</label>
 | 
				
			||||||
						<input id="interval" name="interval" value="{{.MirrorInterval}}">
 | 
											<input id="interval" name="interval" value="{{.MirrorInterval}}">
 | 
				
			||||||
					</div>
 | 
										</div>
 | 
				
			||||||
					<div class="field">
 | 
										<div class="field {{if .Err_MirrorAddress}}error{{end}}">
 | 
				
			||||||
						<label for="mirror_address">{{.i18n.Tr "repo.mirror_address"}}</label>
 | 
											<label for="mirror_address">{{.i18n.Tr "repo.mirror_address"}}</label>
 | 
				
			||||||
						<input id="mirror_address" name="mirror_address" value="{{.Mirror.FullAddress}}" required>
 | 
											<input id="mirror_address" name="mirror_address" value="{{.Mirror.FullAddress}}" required>
 | 
				
			||||||
						<p class="help">{{.i18n.Tr "repo.mirror_address_desc"}}</p>
 | 
											<p class="help">{{.i18n.Tr "repo.mirror_address_desc"}}</p>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -65,7 +65,7 @@
 | 
				
			|||||||
							{{$subJumpablePathName := $entry.GetSubJumpablePathName}}
 | 
												{{$subJumpablePathName := $entry.GetSubJumpablePathName}}
 | 
				
			||||||
							{{$subJumpablePath := SubJumpablePath $subJumpablePathName}}
 | 
												{{$subJumpablePath := SubJumpablePath $subJumpablePathName}}
 | 
				
			||||||
							<span class="octicon octicon-file-directory"></span>
 | 
												<span class="octicon octicon-file-directory"></span>
 | 
				
			||||||
							<a href="{{EscapePound $.TreeLink}}/{{EscapePound $subJumpablePathName}}">
 | 
												<a href="{{EscapePound $.TreeLink}}/{{EscapePound $subJumpablePathName}}" title="{{$subJumpablePathName}}">
 | 
				
			||||||
								{{if eq (len $subJumpablePath) 2}}
 | 
													{{if eq (len $subJumpablePath) 2}}
 | 
				
			||||||
									<span class="jumpable-path">{{index  $subJumpablePath 0}}</span>{{index  $subJumpablePath 1}}
 | 
														<span class="jumpable-path">{{index  $subJumpablePath 0}}</span>{{index  $subJumpablePath 1}}
 | 
				
			||||||
								{{else}}
 | 
													{{else}}
 | 
				
			||||||
@@ -74,7 +74,7 @@
 | 
				
			|||||||
							</a>
 | 
												</a>
 | 
				
			||||||
						{{else}}
 | 
											{{else}}
 | 
				
			||||||
							<span class="octicon octicon-{{EntryIcon $entry}}"></span>
 | 
												<span class="octicon octicon-{{EntryIcon $entry}}"></span>
 | 
				
			||||||
							<a href="{{EscapePound $.TreeLink}}/{{EscapePound $entry.Name}}">{{$entry.Name}}</a>
 | 
												<a href="{{EscapePound $.TreeLink}}/{{EscapePound $entry.Name}}" title="{{$entry.Name}}">{{$entry.Name}}</a>
 | 
				
			||||||
						{{end}}
 | 
											{{end}}
 | 
				
			||||||
					</td>
 | 
										</td>
 | 
				
			||||||
				{{end}}
 | 
									{{end}}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7556,6 +7556,50 @@
 | 
				
			|||||||
      },
 | 
					      },
 | 
				
			||||||
      "x-go-package": "code.gitea.io/gitea/vendor/code.gitea.io/sdk/gitea"
 | 
					      "x-go-package": "code.gitea.io/gitea/vendor/code.gitea.io/sdk/gitea"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 | 
					    "Hook": {
 | 
				
			||||||
 | 
					      "description": "Hook a hook is a web hook when one repository changed",
 | 
				
			||||||
 | 
					      "type": "object",
 | 
				
			||||||
 | 
					      "properties": {
 | 
				
			||||||
 | 
					        "active": {
 | 
				
			||||||
 | 
					          "type": "boolean",
 | 
				
			||||||
 | 
					          "x-go-name": "Active"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "config": {
 | 
				
			||||||
 | 
					          "type": "object",
 | 
				
			||||||
 | 
					          "additionalProperties": {
 | 
				
			||||||
 | 
					            "type": "string"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "x-go-name": "Config"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "created_at": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "format": "date-time",
 | 
				
			||||||
 | 
					          "x-go-name": "Created"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "events": {
 | 
				
			||||||
 | 
					          "type": "array",
 | 
				
			||||||
 | 
					          "items": {
 | 
				
			||||||
 | 
					            "type": "string"
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					          "x-go-name": "Events"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "id": {
 | 
				
			||||||
 | 
					          "type": "integer",
 | 
				
			||||||
 | 
					          "format": "int64",
 | 
				
			||||||
 | 
					          "x-go-name": "ID"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "type": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "x-go-name": "Type"
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        "updated_at": {
 | 
				
			||||||
 | 
					          "type": "string",
 | 
				
			||||||
 | 
					          "format": "date-time",
 | 
				
			||||||
 | 
					          "x-go-name": "Updated"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      "x-go-package": "code.gitea.io/gitea/vendor/code.gitea.io/sdk/gitea"
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
    "Issue": {
 | 
					    "Issue": {
 | 
				
			||||||
      "description": "Issue represents an issue in a repository",
 | 
					      "description": "Issue represents an issue in a repository",
 | 
				
			||||||
      "type": "object",
 | 
					      "type": "object",
 | 
				
			||||||
@@ -8824,10 +8868,7 @@
 | 
				
			|||||||
    "Hook": {
 | 
					    "Hook": {
 | 
				
			||||||
      "description": "Hook",
 | 
					      "description": "Hook",
 | 
				
			||||||
      "schema": {
 | 
					      "schema": {
 | 
				
			||||||
        "type": "array",
 | 
					        "$ref": "#/definitions/Hook"
 | 
				
			||||||
        "items": {
 | 
					 | 
				
			||||||
          "$ref": "#/definitions/Branch"
 | 
					 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "HookList": {
 | 
					    "HookList": {
 | 
				
			||||||
@@ -8835,7 +8876,7 @@
 | 
				
			|||||||
      "schema": {
 | 
					      "schema": {
 | 
				
			||||||
        "type": "array",
 | 
					        "type": "array",
 | 
				
			||||||
        "items": {
 | 
					        "items": {
 | 
				
			||||||
          "$ref": "#/definitions/Branch"
 | 
					          "$ref": "#/definitions/Hook"
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										13
									
								
								vendor/code.gitea.io/git/hook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										13
									
								
								vendor/code.gitea.io/git/hook.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -82,11 +82,20 @@ func (h *Hook) Name() string {
 | 
				
			|||||||
func (h *Hook) Update() error {
 | 
					func (h *Hook) Update() error {
 | 
				
			||||||
	if len(strings.TrimSpace(h.Content)) == 0 {
 | 
						if len(strings.TrimSpace(h.Content)) == 0 {
 | 
				
			||||||
		if isExist(h.path) {
 | 
							if isExist(h.path) {
 | 
				
			||||||
			return os.Remove(h.path)
 | 
								err := os.Remove(h.path)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									return err
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							h.IsActive = false
 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return ioutil.WriteFile(h.path, []byte(strings.Replace(h.Content, "\r", "", -1)), os.ModePerm)
 | 
						err := ioutil.WriteFile(h.path, []byte(strings.Replace(h.Content, "\r", "", -1)), os.ModePerm)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						h.IsActive = true
 | 
				
			||||||
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ListHooks returns a list of Git hooks of given repository.
 | 
					// ListHooks returns a list of Git hooks of given repository.
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										27
									
								
								vendor/code.gitea.io/git/repo_pull.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										27
									
								
								vendor/code.gitea.io/git/repo_pull.go
									
									
									
										generated
									
									
										vendored
									
									
								
							@@ -48,17 +48,22 @@ func (repo *Repository) GetPullRequestInfo(basePath, baseBranch, headBranch stri
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	prInfo := new(PullRequestInfo)
 | 
						prInfo := new(PullRequestInfo)
 | 
				
			||||||
	prInfo.MergeBase, err = repo.GetMergeBase(remoteBranch, headBranch)
 | 
						prInfo.MergeBase, err = repo.GetMergeBase(remoteBranch, headBranch)
 | 
				
			||||||
	if err != nil {
 | 
						if err == nil {
 | 
				
			||||||
		return nil, fmt.Errorf("GetMergeBase: %v", err)
 | 
							// We have a common base
 | 
				
			||||||
	}
 | 
							logs, err := NewCommand("log", prInfo.MergeBase+"..."+headBranch, prettyLogFormat).RunInDirBytes(repo.Path)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
	logs, err := NewCommand("log", prInfo.MergeBase+"..."+headBranch, prettyLogFormat).RunInDirBytes(repo.Path)
 | 
								return nil, err
 | 
				
			||||||
	if err != nil {
 | 
							}
 | 
				
			||||||
		return nil, err
 | 
							prInfo.Commits, err = repo.parsePrettyFormatLogToList(logs)
 | 
				
			||||||
	}
 | 
							if err != nil {
 | 
				
			||||||
	prInfo.Commits, err = repo.parsePrettyFormatLogToList(logs)
 | 
								return nil, fmt.Errorf("parsePrettyFormatLogToList: %v", err)
 | 
				
			||||||
	if err != nil {
 | 
							}
 | 
				
			||||||
		return nil, fmt.Errorf("parsePrettyFormatLogToList: %v", err)
 | 
						} else {
 | 
				
			||||||
 | 
							prInfo.Commits = list.New()
 | 
				
			||||||
 | 
							prInfo.MergeBase, err = GetFullCommitID(repo.Path, remoteBranch)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								prInfo.MergeBase = remoteBranch
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Count number of changed files.
 | 
						// Count number of changed files.
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user