Prevent duplicate Android web-clip apps with the same name (#42664)
Fixes #42641.
This commit is contained in:
@@ -1300,6 +1300,16 @@ func (svc *Service) CreateAndroidWebApp(ctx context.Context, title, startURL str
|
||||
}
|
||||
}
|
||||
|
||||
exists, err := svc.ds.CheckAndroidWebAppNameExists(ctx, title)
|
||||
if err != nil {
|
||||
return "", ctxerr.Wrap(ctx, err, "checking android web app name")
|
||||
}
|
||||
if exists {
|
||||
return "", fleet.ConflictError{
|
||||
Message: fmt.Sprintf(`Couldn't add. Web app with this name ("%s") already exists in this fleet. Please add a web app with a different name or delete the existing app and try again.`, title),
|
||||
}
|
||||
}
|
||||
|
||||
enterprise, err := svc.ds.GetEnterprise(ctx)
|
||||
if err != nil {
|
||||
return "", ctxerr.Wrap(ctx, err, "get android enterprise")
|
||||
|
||||
@@ -2743,3 +2743,14 @@ ORDER BY
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ds *Datastore) CheckAndroidWebAppNameExists(ctx context.Context, name string) (bool, error) {
|
||||
var exists bool
|
||||
err := sqlx.GetContext(ctx, ds.reader(ctx), &exists,
|
||||
`SELECT EXISTS(SELECT 1 FROM vpp_apps WHERE name = ? AND adam_id LIKE ? AND platform = 'android')`,
|
||||
name, fleet.AndroidWebAppPrefix+"%")
|
||||
if err != nil {
|
||||
return false, ctxerr.Wrap(ctx, err, "checking android web app name exists")
|
||||
}
|
||||
return exists, nil
|
||||
}
|
||||
|
||||
@@ -760,6 +760,10 @@ type Datastore interface {
|
||||
CheckConflictingInstallerExists(ctx context.Context, teamID *uint, bundleIdentifier, platform string) (bool, error)
|
||||
CheckConflictingInHouseAppExists(ctx context.Context, teamID *uint, bundleIdentifier, platform string) (bool, error)
|
||||
|
||||
// CheckAndroidWebAppNameExists checks if an Android web app with the given
|
||||
// name already exists in the vpp_apps table (fleet-wide).
|
||||
CheckAndroidWebAppNameExists(ctx context.Context, name string) (bool, error)
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// OperatingSystemsStore
|
||||
|
||||
|
||||
@@ -577,6 +577,8 @@ type CheckConflictingInstallerExistsFunc func(ctx context.Context, teamID *uint,
|
||||
|
||||
type CheckConflictingInHouseAppExistsFunc func(ctx context.Context, teamID *uint, bundleIdentifier string, platform string) (bool, error)
|
||||
|
||||
type CheckAndroidWebAppNameExistsFunc func(ctx context.Context, name string) (bool, error)
|
||||
|
||||
type GetHostOperatingSystemFunc func(ctx context.Context, hostID uint) (*fleet.OperatingSystem, error)
|
||||
|
||||
type ListOperatingSystemsFunc func(ctx context.Context) ([]fleet.OperatingSystem, error)
|
||||
@@ -2671,6 +2673,9 @@ type DataStore struct {
|
||||
CheckConflictingInHouseAppExistsFunc CheckConflictingInHouseAppExistsFunc
|
||||
CheckConflictingInHouseAppExistsFuncInvoked bool
|
||||
|
||||
CheckAndroidWebAppNameExistsFunc CheckAndroidWebAppNameExistsFunc
|
||||
CheckAndroidWebAppNameExistsFuncInvoked bool
|
||||
|
||||
GetHostOperatingSystemFunc GetHostOperatingSystemFunc
|
||||
GetHostOperatingSystemFuncInvoked bool
|
||||
|
||||
@@ -6506,6 +6511,13 @@ func (s *DataStore) CheckConflictingInHouseAppExists(ctx context.Context, teamID
|
||||
return s.CheckConflictingInHouseAppExistsFunc(ctx, teamID, bundleIdentifier, platform)
|
||||
}
|
||||
|
||||
func (s *DataStore) CheckAndroidWebAppNameExists(ctx context.Context, name string) (bool, error) {
|
||||
s.mu.Lock()
|
||||
s.CheckAndroidWebAppNameExistsFuncInvoked = true
|
||||
s.mu.Unlock()
|
||||
return s.CheckAndroidWebAppNameExistsFunc(ctx, name)
|
||||
}
|
||||
|
||||
func (s *DataStore) GetHostOperatingSystem(ctx context.Context, hostID uint) (*fleet.OperatingSystem, error) {
|
||||
s.mu.Lock()
|
||||
s.GetHostOperatingSystemFuncInvoked = true
|
||||
|
||||
@@ -1329,3 +1329,65 @@ func (s *integrationMDMTestSuite) TestAndroidWebAppsCannotSetConfiguration() {
|
||||
}, http.StatusOK, &batchResp)
|
||||
require.Len(t, batchResp.Apps, 2)
|
||||
}
|
||||
|
||||
func (s *integrationMDMTestSuite) TestAndroidWebAppsDuplicateName() {
|
||||
ctx := context.Background()
|
||||
t := s.T()
|
||||
|
||||
s.setSkipWorkerJobs(t)
|
||||
appConf, err := s.ds.AppConfig(ctx)
|
||||
require.NoError(t, err)
|
||||
appConf.MDM.AndroidEnabledAndConfigured = false
|
||||
err = s.ds.SaveAppConfig(ctx, appConf)
|
||||
require.NoError(t, err)
|
||||
|
||||
enterpriseID := s.enableAndroidMDM(t)
|
||||
var count int
|
||||
s.androidAPIClient.EnterprisesWebAppsCreateFunc = func(ctx context.Context, enterpriseName string, app *androidmanagement.WebApp) (*androidmanagement.WebApp, error) {
|
||||
count++
|
||||
id := "dup" + fmt.Sprint(count)
|
||||
return &androidmanagement.WebApp{Name: fmt.Sprintf("enterprises/%s/webApps/com.google.enterprise.webapp.%s", enterpriseID, id)}, nil
|
||||
}
|
||||
s.androidAPIClient.EnterprisesApplicationsFunc = func(ctx context.Context, enterpriseName string, packageName string) (*androidmanagement.Application, error) {
|
||||
return &androidmanagement.Application{IconUrl: "https://example.com/icon.jpg", Title: "Duplicate Web App"}, nil
|
||||
}
|
||||
|
||||
// create a web app
|
||||
body, headers := generateMultipartRequest(t, "", "", nil, s.token, map[string][]string{
|
||||
"title": {"Duplicate Web App"},
|
||||
"url": {"https://example.com"},
|
||||
})
|
||||
var resp createAndroidWebAppResponse
|
||||
res := s.DoRawWithHeaders("POST", "/api/latest/fleet/software/web_apps", body.Bytes(), http.StatusOK, headers)
|
||||
err = json.NewDecoder(res.Body).Decode(&resp)
|
||||
require.NoError(t, err)
|
||||
webAppID := resp.AppStoreID
|
||||
|
||||
// add it to Fleet (populates vpp_apps)
|
||||
var addResp addAppStoreAppResponse
|
||||
s.DoJSON("POST", "/api/latest/fleet/software/app_store_apps", &addAppStoreAppRequest{
|
||||
AppStoreID: webAppID, Platform: fleet.AndroidPlatform,
|
||||
}, http.StatusOK, &addResp)
|
||||
|
||||
// create another web app with the same name
|
||||
body, headers = generateMultipartRequest(t, "", "", nil, s.token, map[string][]string{
|
||||
"title": {"Duplicate Web App"},
|
||||
"url": {"https://different-url.com"},
|
||||
})
|
||||
res = s.DoRawWithHeaders("POST", "/api/latest/fleet/software/web_apps", body.Bytes(), http.StatusConflict, headers)
|
||||
errMsg := extractServerErrorText(res.Body)
|
||||
require.Contains(t, errMsg, `Couldn't add.`)
|
||||
require.Contains(t, errMsg, `"Duplicate Web App"`)
|
||||
require.Contains(t, errMsg, "already exists in this fleet")
|
||||
|
||||
// create a web app with a different name
|
||||
body, headers = generateMultipartRequest(t, "", "", nil, s.token, map[string][]string{
|
||||
"title": {"Different Web App"},
|
||||
"url": {"https://example.com"},
|
||||
})
|
||||
res = s.DoRawWithHeaders("POST", "/api/latest/fleet/software/web_apps", body.Bytes(), http.StatusOK, headers)
|
||||
var resp2 createAndroidWebAppResponse
|
||||
err = json.NewDecoder(res.Body).Decode(&resp2)
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, resp2.AppStoreID)
|
||||
}
|
||||
|
||||
@@ -117,6 +117,9 @@ func TestVPPAuth(t *testing.T) {
|
||||
ds.GetEnterpriseFunc = func(ctx context.Context) (*android.Enterprise, error) {
|
||||
return &android.Enterprise{}, nil
|
||||
}
|
||||
ds.CheckAndroidWebAppNameExistsFunc = func(ctx context.Context, name string) (bool, error) {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Note: these calls always return an error because they're attempting to unmarshal a
|
||||
// non-existent VPP token.
|
||||
|
||||
Reference in New Issue
Block a user