Skip to content

Commit a06d500

Browse files
committed
Merge branch 'main' into add-priority-to-ProtectedBranch
2 parents 6f3308b + 88f5d33 commit a06d500

File tree

11 files changed

+70
-46
lines changed

11 files changed

+70
-46
lines changed

routers/api/v1/api.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ func tokenRequiresScopes(requiredScopeCategories ...auth_model.AccessTokenScopeC
321321
}
322322

323323
if !allow {
324-
ctx.Error(http.StatusForbidden, "tokenRequiresScope", fmt.Sprintf("token does not have at least one of required scope(s): %v", requiredScopes))
324+
ctx.Error(http.StatusForbidden, "tokenRequiresScope", fmt.Sprintf("token does not have at least one of required scope(s), required=%v, token scope=%v", requiredScopes, scope))
325325
return
326326
}
327327

services/oauth2_provider/access_token.go

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -74,26 +74,32 @@ type AccessTokenResponse struct {
7474
// GrantAdditionalScopes returns valid scopes coming from grant
7575
func GrantAdditionalScopes(grantScopes string) auth.AccessTokenScope {
7676
// scopes_supported from templates/user/auth/oidc_wellknown.tmpl
77-
scopesSupported := []string{
77+
generalScopesSupported := []string{
7878
"openid",
7979
"profile",
8080
"email",
8181
"groups",
8282
}
8383

84-
var tokenScopes []string
85-
for _, tokenScope := range strings.Split(grantScopes, " ") {
86-
if slices.Index(scopesSupported, tokenScope) == -1 {
87-
tokenScopes = append(tokenScopes, tokenScope)
84+
var accessScopes []string // the scopes for access control, but not for general information
85+
for _, scope := range strings.Split(grantScopes, " ") {
86+
if scope != "" && !slices.Contains(generalScopesSupported, scope) {
87+
accessScopes = append(accessScopes, scope)
8888
}
8989
}
9090

9191
// since version 1.22, access tokens grant full access to the API
9292
// with this access is reduced only if additional scopes are provided
93-
accessTokenScope := auth.AccessTokenScope(strings.Join(tokenScopes, ","))
94-
if accessTokenWithAdditionalScopes, err := accessTokenScope.Normalize(); err == nil && len(tokenScopes) > 0 {
95-
return accessTokenWithAdditionalScopes
93+
if len(accessScopes) > 0 {
94+
accessTokenScope := auth.AccessTokenScope(strings.Join(accessScopes, ","))
95+
if normalizedAccessTokenScope, err := accessTokenScope.Normalize(); err == nil {
96+
return normalizedAccessTokenScope
97+
}
98+
// TODO: if there are invalid access scopes (err != nil),
99+
// then it is treated as "all", maybe in the future we should make it stricter to return an error
100+
// at the moment, to avoid breaking 1.22 behavior, invalid tokens are also treated as "all"
96101
}
102+
// fallback, empty access scope is treated as "all" access
97103
return auth.AccessTokenScopeAll
98104
}
99105

services/oauth2_provider/additional_scopes_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ func TestGrantAdditionalScopes(t *testing.T) {
1414
grantScopes string
1515
expectedScopes string
1616
}{
17+
{"", "all"}, // for old tokens without scope, treat it as "all"
1718
{"openid profile email", "all"},
1819
{"openid profile email groups", "all"},
1920
{"openid profile email all", "all"},
@@ -22,12 +23,14 @@ func TestGrantAdditionalScopes(t *testing.T) {
2223
{"read:user read:repository", "read:repository,read:user"},
2324
{"read:user write:issue public-only", "public-only,write:issue,read:user"},
2425
{"openid profile email read:user", "read:user"},
26+
27+
// TODO: at the moment invalid tokens are treated as "all" to avoid breaking 1.22 behavior (more details are in GrantAdditionalScopes)
2528
{"read:invalid_scope", "all"},
2629
{"read:invalid_scope,write:scope_invalid,just-plain-wrong", "all"},
2730
}
2831

2932
for _, test := range tests {
30-
t.Run(test.grantScopes, func(t *testing.T) {
33+
t.Run("scope:"+test.grantScopes, func(t *testing.T) {
3134
result := GrantAdditionalScopes(test.grantScopes)
3235
assert.Equal(t, test.expectedScopes, string(result))
3336
})

templates/repo/diff/box.tmpl

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -164,24 +164,22 @@
164164
<input type="checkbox" name="{{$file.GetDiffFileName}}" autocomplete="off"{{if $file.IsViewed}} checked{{end}}> {{ctx.Locale.Tr "repo.pulls.has_viewed_file"}}
165165
</label>
166166
{{end}}
167-
<div class="ui dropdown basic">
168-
{{svg "octicon-kebab-horizontal" 18 "icon tw-mx-2"}}
169-
<div class="ui menu">
170-
{{if not (or $file.IsIncomplete $file.IsBin $file.IsSubmodule)}}
171-
<button class="unescape-button item">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</button>
172-
<button class="escape-button tw-hidden item">{{ctx.Locale.Tr "repo.escape_control_characters"}}</button>
173-
{{end}}
174-
{{if and (not $file.IsSubmodule) (not $.PageIsWiki)}}
175-
{{if $file.IsDeleted}}
176-
<a class="item" rel="nofollow" href="{{$.BeforeSourcePath}}/{{PathEscapeSegments .Name}}">{{ctx.Locale.Tr "repo.diff.view_file"}}</a>
177-
{{else}}
178-
<a class="item" rel="nofollow" href="{{$.SourcePath}}/{{PathEscapeSegments .Name}}">{{ctx.Locale.Tr "repo.diff.view_file"}}</a>
179-
{{if and $.Repository.CanEnableEditor $.CanEditFile (not $file.IsLFSFile) (not $file.IsBin)}}
180-
<a class="item" rel="nofollow" href="{{$.HeadRepoLink}}/_edit/{{PathEscapeSegments $.HeadBranchName}}/{{PathEscapeSegments $file.Name}}?return_uri={{print $.BackToLink "#diff-" $file.NameHash | QueryEscape}}">{{ctx.Locale.Tr "repo.editor.edit_this_file"}}</a>
181-
{{end}}
167+
<button class="btn diff-header-popup-btn tw-p-1">{{svg "octicon-kebab-horizontal" 18}}</button>
168+
<div class="tippy-target">
169+
{{if not (or $file.IsIncomplete $file.IsBin $file.IsSubmodule)}}
170+
<button class="unescape-button item" data-file-content-elem-id="diff-{{$file.NameHash}}">{{ctx.Locale.Tr "repo.unescape_control_characters"}}</button>
171+
<button class="escape-button tw-hidden item" data-file-content-elem-id="diff-{{$file.NameHash}}">{{ctx.Locale.Tr "repo.escape_control_characters"}}</button>
172+
{{end}}
173+
{{if and (not $file.IsSubmodule) (not $.PageIsWiki)}}
174+
{{if $file.IsDeleted}}
175+
<a class="item" rel="nofollow" href="{{$.BeforeSourcePath}}/{{PathEscapeSegments .Name}}">{{ctx.Locale.Tr "repo.diff.view_file"}}</a>
176+
{{else}}
177+
<a class="item" rel="nofollow" href="{{$.SourcePath}}/{{PathEscapeSegments .Name}}">{{ctx.Locale.Tr "repo.diff.view_file"}}</a>
178+
{{if and $.Repository.CanEnableEditor $.CanEditFile (not $file.IsLFSFile) (not $file.IsBin)}}
179+
<a class="item" rel="nofollow" href="{{$.HeadRepoLink}}/_edit/{{PathEscapeSegments $.HeadBranchName}}/{{PathEscapeSegments $file.Name}}?return_uri={{print $.BackToLink "#diff-" $file.NameHash | QueryEscape}}">{{ctx.Locale.Tr "repo.editor.edit_this_file"}}</a>
182180
{{end}}
183181
{{end}}
184-
</div>
182+
{{end}}
185183
</div>
186184
</div>
187185
</h4>

tests/integration/oauth_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -565,7 +565,7 @@ func TestOAuth_GrantScopesReadUserFailRepos(t *testing.T) {
565565

566566
errorParsed := new(errorResponse)
567567
require.NoError(t, json.Unmarshal(errorResp.Body.Bytes(), errorParsed))
568-
assert.Contains(t, errorParsed.Message, "token does not have at least one of required scope(s): [read:repository]")
568+
assert.Contains(t, errorParsed.Message, "token does not have at least one of required scope(s), required=[read:repository]")
569569
}
570570

571571
func TestOAuth_GrantScopesReadRepositoryFailOrganization(t *testing.T) {
@@ -708,7 +708,7 @@ func TestOAuth_GrantScopesReadRepositoryFailOrganization(t *testing.T) {
708708

709709
errorParsed := new(errorResponse)
710710
require.NoError(t, json.Unmarshal(errorResp.Body.Bytes(), errorParsed))
711-
assert.Contains(t, errorParsed.Message, "token does not have at least one of required scope(s): [read:user read:organization]")
711+
assert.Contains(t, errorParsed.Message, "token does not have at least one of required scope(s), required=[read:user read:organization]")
712712
}
713713

714714
func TestOAuth_GrantScopesClaimPublicOnlyGroups(t *testing.T) {

tests/integration/pull_compare_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ func TestPullCompare_EnableAllowEditsFromMaintainer(t *testing.T) {
9797
user2Session := loginUser(t, "user2")
9898
resp = user2Session.MakeRequest(t, NewRequest(t, "GET", fmt.Sprintf("%s/files", prURL)), http.StatusOK)
9999
htmlDoc := NewHTMLParser(t, resp.Body)
100-
nodes := htmlDoc.doc.Find(".diff-file-box[data-new-filename=\"README.md\"] .diff-file-header-actions .dropdown .menu a")
100+
nodes := htmlDoc.doc.Find(".diff-file-box[data-new-filename=\"README.md\"] .diff-file-header-actions .tippy-target a")
101101
if assert.Equal(t, 1, nodes.Length()) {
102102
// there is only "View File" button, no "Edit File" button
103103
assert.Equal(t, "View File", nodes.First().Text())
@@ -121,7 +121,7 @@ func TestPullCompare_EnableAllowEditsFromMaintainer(t *testing.T) {
121121
// user2 (admin of repo3) goes to the PR files page again
122122
resp = user2Session.MakeRequest(t, NewRequest(t, "GET", fmt.Sprintf("%s/files", prURL)), http.StatusOK)
123123
htmlDoc = NewHTMLParser(t, resp.Body)
124-
nodes = htmlDoc.doc.Find(".diff-file-box[data-new-filename=\"README.md\"] .diff-file-header-actions .dropdown .menu a")
124+
nodes = htmlDoc.doc.Find(".diff-file-box[data-new-filename=\"README.md\"] .diff-file-header-actions .tippy-target a")
125125
if assert.Equal(t, 2, nodes.Length()) {
126126
// there are "View File" button and "Edit File" button
127127
assert.Equal(t, "View File", nodes.First().Text())

web_src/css/modules/tippy.css

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,10 @@
7777
align-items: center;
7878
padding: 9px 18px;
7979
color: inherit;
80+
background: inherit;
8081
text-decoration: none;
8182
gap: 10px;
83+
width: 100%;
8284
}
8385

8486
.tippy-box[data-theme="menu"] .item:hover {

web_src/js/features/repo-diff.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
} from '../utils/dom.ts';
1919
import {POST, GET} from '../modules/fetch.ts';
2020
import {fomanticQuery} from '../modules/fomantic/base.ts';
21+
import {createTippy} from '../modules/tippy.ts';
2122

2223
const {pageData, i18n} = window.config;
2324

@@ -140,12 +141,22 @@ export function initRepoDiffConversationNav() {
140141
});
141142
}
142143

144+
function initDiffHeaderPopup() {
145+
for (const btn of document.querySelectorAll('.diff-header-popup-btn:not([data-header-popup-initialized])')) {
146+
btn.setAttribute('data-header-popup-initialized', '');
147+
const popup = btn.nextElementSibling;
148+
if (!popup?.matches('.tippy-target')) throw new Error('Popup element not found');
149+
createTippy(btn, {content: popup, theme: 'menu', placement: 'bottom', trigger: 'click', interactive: true, hideOnClick: true});
150+
}
151+
}
152+
143153
// Will be called when the show more (files) button has been pressed
144154
function onShowMoreFiles() {
145155
initRepoIssueContentHistory();
146156
initViewedCheckboxListenerFor();
147157
countAndUpdateViewedFiles();
148158
initImageDiff();
159+
initDiffHeaderPopup();
149160
}
150161

151162
export async function loadMoreFiles(url) {
@@ -221,6 +232,7 @@ export function initRepoDiffView() {
221232
initDiffFileList();
222233
initDiffCommitSelect();
223234
initRepoDiffShowMore();
235+
initDiffHeaderPopup();
224236
initRepoDiffFileViewToggle();
225237
initViewedCheckboxListenerFor();
226238
initExpandAndCollapseFilesButton();

web_src/js/features/repo-unicode-escape.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import {hideElem, queryElemSiblings, showElem, toggleElem} from '../utils/dom.ts';
1+
import {addDelegatedEventListener, hideElem, queryElemSiblings, showElem, toggleElem} from '../utils/dom.ts';
22

33
export function initUnicodeEscapeButton() {
4-
document.addEventListener('click', (e) => {
5-
const btn = e.target.closest('.escape-button, .unescape-button, .toggle-escape-button');
6-
if (!btn) return;
7-
4+
addDelegatedEventListener(document, 'click', '.escape-button, .unescape-button, .toggle-escape-button', (btn, e) => {
85
e.preventDefault();
96

10-
const fileContent = btn.closest('.file-content, .non-diff-file-content');
7+
const fileContentElemId = btn.getAttribute('data-file-content-elem-id');
8+
const fileContent = fileContentElemId ?
9+
document.querySelector(`#${fileContentElemId}`) :
10+
btn.closest('.file-content, .non-diff-file-content');
1111
const fileView = fileContent?.querySelectorAll('.file-code, .file-view');
1212
if (btn.matches('.escape-button')) {
1313
for (const el of fileView) el.classList.add('unicode-escaped');

web_src/js/markup/mermaid.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,12 @@ export async function renderMermaid(): Promise<void> {
5858
mermaidBlock.append(btn);
5959

6060
const updateIframeHeight = () => {
61-
iframe.style.height = `${iframe.contentWindow.document.body.clientHeight}px`;
61+
const body = iframe.contentWindow?.document?.body;
62+
if (body) {
63+
iframe.style.height = `${body.clientHeight}px`;
64+
}
6265
};
6366

64-
// update height when element's visibility state changes, for example when the diagram is inside
65-
// a <details> + <summary> block and the <details> block becomes visible upon user interaction, it
66-
// would initially set a incorrect height and the correct height is set during this callback.
67-
(new IntersectionObserver(() => {
68-
updateIframeHeight();
69-
}, {root: document.documentElement})).observe(iframe);
70-
7167
iframe.addEventListener('load', () => {
7268
pre.replaceWith(mermaidBlock);
7369
mermaidBlock.classList.remove('tw-hidden');
@@ -76,6 +72,13 @@ export async function renderMermaid(): Promise<void> {
7672
mermaidBlock.classList.remove('is-loading');
7773
iframe.classList.remove('tw-invisible');
7874
}, 0);
75+
76+
// update height when element's visibility state changes, for example when the diagram is inside
77+
// a <details> + <summary> block and the <details> block becomes visible upon user interaction, it
78+
// would initially set a incorrect height and the correct height is set during this callback.
79+
(new IntersectionObserver(() => {
80+
updateIframeHeight();
81+
}, {root: document.documentElement})).observe(iframe);
7982
});
8083

8184
document.body.append(mermaidBlock);

0 commit comments

Comments
 (0)