mirror of
https://github.com/coder/coder.git
synced 2025-07-15 22:20:27 +00:00
feat: Allow admins to access member workspace terminals (#2114)
* allow workspace update permissions to access agents * do not show app links to users without workspace update access * address CR comments * initialize machine context in the hook * revert scoped connected status check
This commit is contained in:
@ -292,6 +292,7 @@ func New(options *Options) *API {
|
||||
r.Use(
|
||||
apiKeyMiddleware,
|
||||
httpmw.ExtractWorkspaceAgentParam(options.Database),
|
||||
httpmw.ExtractWorkspaceParam(options.Database),
|
||||
)
|
||||
r.Get("/", api.workspaceAgent)
|
||||
r.Get("/dial", api.workspaceAgentDial)
|
||||
|
@ -153,10 +153,7 @@ func TestAuthorizeAllEndpoints(t *testing.T) {
|
||||
"GET:/api/v2/workspaceagents/me/listen": {NoAuthorize: true},
|
||||
"GET:/api/v2/workspaceagents/me/metadata": {NoAuthorize: true},
|
||||
"GET:/api/v2/workspaceagents/me/turn": {NoAuthorize: true},
|
||||
"GET:/api/v2/workspaceagents/{workspaceagent}": {NoAuthorize: true},
|
||||
"GET:/api/v2/workspaceagents/{workspaceagent}/dial": {NoAuthorize: true},
|
||||
"GET:/api/v2/workspaceagents/{workspaceagent}/iceservers": {NoAuthorize: true},
|
||||
"GET:/api/v2/workspaceagents/{workspaceagent}/pty": {NoAuthorize: true},
|
||||
"GET:/api/v2/workspaceagents/{workspaceagent}/turn": {NoAuthorize: true},
|
||||
|
||||
// These endpoints have more assertions. This is good, add more endpoints to assert if you can!
|
||||
@ -210,6 +207,18 @@ func TestAuthorizeAllEndpoints(t *testing.T) {
|
||||
AssertAction: rbac.ActionRead,
|
||||
AssertObject: workspaceRBACObj,
|
||||
},
|
||||
"GET:/api/v2/workspaceagents/{workspaceagent}": {
|
||||
AssertAction: rbac.ActionRead,
|
||||
AssertObject: workspaceRBACObj,
|
||||
},
|
||||
"GET:/api/v2/workspaceagents/{workspaceagent}/dial": {
|
||||
AssertAction: rbac.ActionUpdate,
|
||||
AssertObject: workspaceRBACObj,
|
||||
},
|
||||
"GET:/api/v2/workspaceagents/{workspaceagent}/pty": {
|
||||
AssertAction: rbac.ActionUpdate,
|
||||
AssertObject: workspaceRBACObj,
|
||||
},
|
||||
"GET:/api/v2/workspaces/": {
|
||||
StatusCode: http.StatusOK,
|
||||
AssertAction: rbac.ActionRead,
|
||||
@ -378,6 +387,7 @@ func TestAuthorizeAllEndpoints(t *testing.T) {
|
||||
route = strings.ReplaceAll(route, "{workspacebuild}", workspace.LatestBuild.ID.String())
|
||||
route = strings.ReplaceAll(route, "{workspacename}", workspace.Name)
|
||||
route = strings.ReplaceAll(route, "{workspacebuildname}", workspace.LatestBuild.Name)
|
||||
route = strings.ReplaceAll(route, "{workspaceagent}", workspaceResources[0].Agents[0].ID.String())
|
||||
route = strings.ReplaceAll(route, "{template}", template.ID.String())
|
||||
route = strings.ReplaceAll(route, "{hash}", file.Hash)
|
||||
route = strings.ReplaceAll(route, "{workspaceresource}", workspaceResources[0].ID.String())
|
||||
|
@ -37,11 +37,11 @@ type userRolesKey struct{}
|
||||
// AuthorizationUserRoles returns the roles used for authorization.
|
||||
// Comes from the ExtractAPIKey handler.
|
||||
func AuthorizationUserRoles(r *http.Request) database.GetAuthorizationUserRolesRow {
|
||||
apiKey, ok := r.Context().Value(userRolesKey{}).(database.GetAuthorizationUserRolesRow)
|
||||
userRoles, ok := r.Context().Value(userRolesKey{}).(database.GetAuthorizationUserRolesRow)
|
||||
if !ok {
|
||||
panic("developer error: user roles middleware not provided")
|
||||
}
|
||||
return apiKey
|
||||
return userRoles
|
||||
}
|
||||
|
||||
// OAuth2Configs is a collection of configurations for OAuth-based authentication.
|
||||
|
@ -6,6 +6,8 @@ import (
|
||||
"errors"
|
||||
"net/http"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/httpapi"
|
||||
)
|
||||
@ -74,24 +76,9 @@ func ExtractWorkspaceAgentParam(db database.Store) func(http.Handler) http.Handl
|
||||
})
|
||||
return
|
||||
}
|
||||
workspace, err := db.GetWorkspaceByID(r.Context(), build.WorkspaceID)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
Message: "Internal error fetching workspace.",
|
||||
Detail: err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
apiKey := APIKey(r)
|
||||
if apiKey.UserID != workspace.OwnerID {
|
||||
httpapi.Write(rw, http.StatusUnauthorized, httpapi.Response{
|
||||
Message: "Getting non-personal agents isn't supported.",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), workspaceAgentParamContextKey{}, agent)
|
||||
chi.RouteContext(ctx).URLParams.Add("workspace", build.WorkspaceID.String())
|
||||
next.ServeHTTP(rw, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"github.com/coder/coder/coderd/database"
|
||||
"github.com/coder/coder/coderd/httpapi"
|
||||
"github.com/coder/coder/coderd/httpmw"
|
||||
"github.com/coder/coder/coderd/rbac"
|
||||
"github.com/coder/coder/coderd/turnconn"
|
||||
"github.com/coder/coder/codersdk"
|
||||
"github.com/coder/coder/peer"
|
||||
@ -31,6 +32,10 @@ import (
|
||||
|
||||
func (api *API) workspaceAgent(rw http.ResponseWriter, r *http.Request) {
|
||||
workspaceAgent := httpmw.WorkspaceAgentParam(r)
|
||||
workspace := httpmw.WorkspaceParam(r)
|
||||
if !api.Authorize(rw, r, rbac.ActionRead, workspace) {
|
||||
return
|
||||
}
|
||||
dbApps, err := api.Database.GetWorkspaceAppsByAgentID(r.Context(), workspaceAgent.ID)
|
||||
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
@ -58,6 +63,10 @@ func (api *API) workspaceAgentDial(rw http.ResponseWriter, r *http.Request) {
|
||||
defer api.websocketWaitGroup.Done()
|
||||
|
||||
workspaceAgent := httpmw.WorkspaceAgentParam(r)
|
||||
workspace := httpmw.WorkspaceParam(r)
|
||||
if !api.Authorize(rw, r, rbac.ActionUpdate, workspace) {
|
||||
return
|
||||
}
|
||||
apiAgent, err := convertWorkspaceAgent(workspaceAgent, nil, api.AgentConnectionUpdateFrequency)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
@ -369,6 +378,10 @@ func (api *API) workspaceAgentPTY(rw http.ResponseWriter, r *http.Request) {
|
||||
defer api.websocketWaitGroup.Done()
|
||||
|
||||
workspaceAgent := httpmw.WorkspaceAgentParam(r)
|
||||
workspace := httpmw.WorkspaceParam(r)
|
||||
if !api.Authorize(rw, r, rbac.ActionUpdate, workspace) {
|
||||
return
|
||||
}
|
||||
apiAgent, err := convertWorkspaceAgent(workspaceAgent, nil, api.AgentConnectionUpdateFrequency)
|
||||
if err != nil {
|
||||
httpapi.Write(rw, http.StatusInternalServerError, httpapi.Response{
|
||||
|
Reference in New Issue
Block a user