Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 36 additions & 22 deletions web/sharings/sharings.go
Original file line number Diff line number Diff line change
Expand Up @@ -686,6 +686,20 @@ func renderAlreadyAccepted(c echo.Context, inst *instance.Instance, cozyURL stri
})
}

func renderInvalidSharing(c echo.Context, inst *instance.Instance) error {
return c.Render(http.StatusBadRequest, "error.html", echo.Map{
"Domain": inst.ContextualDomain(),
"ContextName": inst.ContextName,
"Locale": inst.Locale,
"Title": inst.TemplateTitle(),
"Favicon": middlewares.Favicon(inst),
"Illustration": "/images/generic-error.svg",
"Error": "Error Invalid sharing",
"SupportEmail": inst.SupportEmailAddress(),
"SupportPageURL": inst.SupportPageURL(),
})
}

func renderDiscoveryForm(c echo.Context, inst *instance.Instance, code int, sharingID, state, sharecode, shortcut string, m *sharing.Member) error {
publicName, _ := settings.PublicName(inst)
fqdn := strings.TrimPrefix(m.Instance, "https://")
Expand Down Expand Up @@ -774,6 +788,20 @@ func renderDiscoveryForm(c echo.Context, inst *instance.Instance, code int, shar
})
}

func discoveryStateForMember(s *sharing.Sharing, currentState string, m *sharing.Member) (string, error) {
if currentState != "" {
return currentState, nil
}
if m == nil {
return "", sharing.ErrInvalidSharing
}
credentials := s.FindCredentials(m)
if credentials == nil || credentials.State == "" {
return "", sharing.ErrInvalidSharing
}
return credentials.State, nil
}

// GetDiscovery displays a form where a recipient can give the address of their
// cozy instance
func GetDiscovery(c echo.Context) error {
Expand All @@ -785,17 +813,7 @@ func GetDiscovery(c echo.Context) error {

s, err := sharing.FindSharing(inst, sharingID)
if err != nil {
return c.Render(http.StatusBadRequest, "error.html", echo.Map{
"Domain": inst.ContextualDomain(),
"ContextName": inst.ContextName,
"Locale": inst.Locale,
"Title": inst.TemplateTitle(),
"Favicon": middlewares.Favicon(inst),
"Illustration": "/images/generic-error.svg",
"Error": "Error Invalid sharing",
"SupportEmail": inst.SupportEmailAddress(),
"SupportPageURL": inst.SupportPageURL(),
})
return renderInvalidSharing(c, inst)
}

m := &sharing.Member{}
Expand All @@ -806,23 +824,16 @@ func GetDiscovery(c echo.Context) error {
m, err = s.FindMemberByState(state)
}
if err != nil || m.Status == sharing.MemberStatusRevoked {
return c.Render(http.StatusBadRequest, "error.html", echo.Map{
"Domain": inst.ContextualDomain(),
"ContextName": inst.ContextName,
"Locale": inst.Locale,
"Title": inst.TemplateTitle(),
"Favicon": middlewares.Favicon(inst),
"Illustration": "/images/generic-error.svg",
"Error": "Error Invalid sharing",
"SupportEmail": inst.SupportEmailAddress(),
"SupportPageURL": inst.SupportPageURL(),
})
return renderInvalidSharing(c, inst)
}
if m.Status != sharing.MemberStatusMailNotSent &&
m.Status != sharing.MemberStatusPendingInvitation &&
m.Status != sharing.MemberStatusSeen {
return renderAlreadyAccepted(c, inst, m.Instance)
}
if state, err = discoveryStateForMember(s, state, m); err != nil {
return renderInvalidSharing(c, inst)
}
}

if m.Instance != "" {
Expand Down Expand Up @@ -886,6 +897,9 @@ func PostDiscovery(c echo.Context) error {
return wrapErrors(err)
}
}
if state, err = discoveryStateForMember(s, state, member); err != nil {
return wrapErrors(err)
}
Comment on lines +900 to +902

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we rely solely on the state that was sent by the client at this point?

If it was missing, the state should now have been computed in GetDiscovery() and sent as parameter to PostDiscovery().

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it was "just in case" fix, I can't imagine flow when we have post with empty state here, but in the same time I didn't find a clean separation between different flows with sharecode and state in this function, so I've decided add this "helper" to have PostDiscovery wtihout state.
It's not connected to the initial buf, so if you also don't have in mind such kind of flows and this lines confuse you, I can remove them.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have all the flows in mind. I'm even surprised to see the member can either be computed from sharecode or state at this point since having a state seems necessary…

If I had all the pieces together I'd say let's remove all sharecode use in this function but maybe there are weird cases.

So you're right, let's keep this "just in case" fix.

if strings.Contains(cozyURL, "@") {
return renderDiscoveryForm(c, inst, http.StatusPreconditionFailed, sharingID, state, sharecode, shortcut, member)
}
Expand Down
51 changes: 51 additions & 0 deletions web/sharings/sharings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,57 @@ func TestSharings(t *testing.T) {
assert.Contains(t, discoveryLink, "/preview?sharecode=")
})

t.Run("DiscoveryWithPreviewOIDCLinkUsesMemberState", func(t *testing.T) {
u, err := url.Parse(discoveryLink)
require.NoError(t, err)
sharecode := u.Query().Get("sharecode")
require.NotEmpty(t, sharecode)

s, err := sharing.FindSharing(aliceInstance, sharingID)
require.NoError(t, err)
member, err := s.FindMemberBySharecode(aliceInstance, sharecode)
require.NoError(t, err)
credentials := s.FindCredentials(member)
require.NotNil(t, credentials)
require.NotEmpty(t, credentials.State)

conf := config.GetConfig()
if conf.Authentication == nil {
conf.Authentication = make(map[string]interface{})
}
oidcContext := "preview-oidc-test"
originalContext := aliceInstance.ContextName
previousAuth, hadPreviousAuth := conf.Authentication[oidcContext]
conf.Authentication[oidcContext] = map[string]interface{}{
"oidc": map[string]interface{}{
"client_id": "preview-oidc-client",
},
}
aliceInstance.ContextName = oidcContext
require.NoError(t, instance.Update(aliceInstance))
t.Cleanup(func() {
if hadPreviousAuth {
conf.Authentication[oidcContext] = previousAuth
} else {
delete(conf.Authentication, oidcContext)
}
aliceInstance.ContextName = originalContext
if err := instance.Update(aliceInstance); err != nil {
t.Errorf("cannot restore Alice context: %s", err)
}
})

eA := httpexpect.Default(t, tsA.URL)
body := eA.GET("/sharings/"+sharingID+"/discovery").
WithQuery("sharecode", sharecode).
Expect().Status(200).
Body().Raw()

assert.Contains(t, body, "/oidc/sharing?")
assert.Contains(t, body, "state="+credentials.State)
assert.NotContains(t, body, "state=&")
})

t.Run("DiscoveryWithPreview", func(t *testing.T) {
u, err := url.Parse(discoveryLink)
assert.NoError(t, err)
Expand Down
Loading