diff --git a/.circleci/config.yml b/.circleci/config.yml index 3aedd49c935..9b729ecfa15 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -221,6 +221,8 @@ workflows: jobs: - build-all: filters: *filter-not-release + - build-enterprise: + filters: *filter-not-release - codespell: filters: *filter-not-release - gometalinter: diff --git a/pkg/api/api.go b/pkg/api/api.go index 39dae56eb69..78c7aaf3f39 100644 --- a/pkg/api/api.go +++ b/pkg/api/api.go @@ -9,7 +9,14 @@ import ( m "github.com/grafana/grafana/pkg/models" ) -// Register adds http routes +func (hs *HTTPServer) applyRoutes() { + hs.RouteRegister.Register(hs.macaron) + + InitAppPluginRoutes(hs.macaron) + + hs.macaron.NotFound(NotFoundHandler) +} + func (hs *HTTPServer) registerRoutes() { macaronR := hs.macaron reqSignedIn := middleware.Auth(&middleware.AuthOptions{ReqSignedIn: true}) @@ -393,10 +400,4 @@ func (hs *HTTPServer) registerRoutes() { // streams //r.Post("/api/streams/push", reqSignedIn, bind(dtos.StreamMessage{}), liveConn.PushToStream) - - r.Register(macaronR) - - InitAppPluginRoutes(macaronR) - - macaronR.NotFound(NotFoundHandler) } diff --git a/pkg/api/frontendsettings.go b/pkg/api/frontendsettings.go index e7272e68997..06e6405baaf 100644 --- a/pkg/api/frontendsettings.go +++ b/pkg/api/frontendsettings.go @@ -132,6 +132,7 @@ func getFrontendSettingsMap(c *m.ReqContext) (map[string]interface{}, error) { } jsonObj := map[string]interface{}{ + "enterprise": setting.Enterprise, "defaultDatasource": defaultDatasource, "datasources": datasources, "panels": panels, diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index e1a10fb468f..627192bb69b 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -33,7 +33,11 @@ import ( ) func init() { - registry.RegisterService(&HTTPServer{}) + registry.Register(®istry.Descriptor{ + Name: "HTTPServer", + Instance: &HTTPServer{}, + InitPriority: registry.High, + }) } type HTTPServer struct { @@ -54,6 +58,10 @@ func (hs *HTTPServer) Init() error { hs.log = log.New("http.server") hs.cache = gocache.New(5*time.Minute, 10*time.Minute) + hs.streamManager = live.NewStreamManager() + hs.macaron = hs.newMacaron() + hs.registerRoutes() + return nil } @@ -61,10 +69,7 @@ func (hs *HTTPServer) Run(ctx context.Context) error { var err error hs.context = ctx - hs.streamManager = live.NewStreamManager() - hs.macaron = hs.newMacaron() - hs.registerRoutes() - + hs.applyRoutes() hs.streamManager.Run(ctx) listenAddr := fmt.Sprintf("%s:%s", setting.HttpAddr, setting.HttpPort) diff --git a/pkg/login/ext_user.go b/pkg/login/ext_user.go index e1d5e3e3b48..d2f1aa1ff52 100644 --- a/pkg/login/ext_user.go +++ b/pkg/login/ext_user.go @@ -66,7 +66,21 @@ func UpsertUser(cmd *m.UpsertUserCommand) error { } } - return syncOrgRoles(cmd.Result, extUser) + err = syncOrgRoles(cmd.Result, extUser) + if err != nil { + return err + } + + err = bus.Dispatch(&m.SyncTeamsCommand{ + User: cmd.Result, + ExternalUser: extUser, + }) + + if err == bus.ErrHandlerNotFound { + return nil + } + + return err } func createUser(extUser *m.ExternalUserInfo) (*m.User, error) { diff --git a/pkg/login/ldap.go b/pkg/login/ldap.go index 026a94fa43e..bdf87b2db54 100644 --- a/pkg/login/ldap.go +++ b/pkg/login/ldap.go @@ -163,6 +163,7 @@ func (a *ldapAuther) GetGrafanaUserFor(ctx *m.ReqContext, ldapUser *LdapUserInfo Name: fmt.Sprintf("%s %s", ldapUser.FirstName, ldapUser.LastName), Login: ldapUser.Username, Email: ldapUser.Email, + Groups: ldapUser.MemberOf, OrgRoles: map[int64]m.RoleType{}, } @@ -194,6 +195,7 @@ func (a *ldapAuther) GetGrafanaUserFor(ctx *m.ReqContext, ldapUser *LdapUserInfo ExternalUser: extUser, SignupAllowed: setting.LdapAllowSignup, } + err := bus.Dispatch(userQuery) if err != nil { return nil, err diff --git a/pkg/login/ldap_test.go b/pkg/login/ldap_test.go index 34932926406..5080840704e 100644 --- a/pkg/login/ldap_test.go +++ b/pkg/login/ldap_test.go @@ -1,6 +1,7 @@ package login import ( + "context" "crypto/tls" "testing" @@ -14,6 +15,14 @@ func TestLdapAuther(t *testing.T) { Convey("When translating ldap user to grafana user", t, func() { + var user1 = &m.User{} + + bus.AddHandlerCtx("test", func(ctx context.Context, cmd *m.UpsertUserCommand) error { + cmd.Result = user1 + cmd.Result.Login = "torkelo" + return nil + }) + Convey("Given no ldap group map match", func() { ldapAuther := NewLdapAuthenticator(&LdapServerConf{ LdapGroups: []*LdapGroupToOrgRole{{}}, @@ -23,8 +32,6 @@ func TestLdapAuther(t *testing.T) { So(err, ShouldEqual, ErrInvalidCredentials) }) - var user1 = &m.User{} - ldapAutherScenario("Given wildcard group match", func(sc *scenarioContext) { ldapAuther := NewLdapAuthenticator(&LdapServerConf{ LdapGroups: []*LdapGroupToOrgRole{ @@ -96,7 +103,6 @@ func TestLdapAuther(t *testing.T) { }) Convey("When syncing ldap groups to grafana org roles", t, func() { - ldapAutherScenario("given no current user orgs", func(sc *scenarioContext) { ldapAuther := NewLdapAuthenticator(&LdapServerConf{ LdapGroups: []*LdapGroupToOrgRole{ @@ -322,6 +328,10 @@ func ldapAutherScenario(desc string, fn scenarioFunc) { bus.AddHandler("test", UpsertUser) + bus.AddHandlerCtx("test", func(ctx context.Context, cmd *m.SyncTeamsCommand) error { + return nil + }) + bus.AddHandler("test", func(cmd *m.GetUserByAuthInfoQuery) error { sc.getUserByAuthInfoQuery = cmd sc.getUserByAuthInfoQuery.Result = &m.User{Login: cmd.Login} diff --git a/pkg/models/team_member.go b/pkg/models/team_member.go index 19cf657292d..9434dad8ecd 100644 --- a/pkg/models/team_member.go +++ b/pkg/models/team_member.go @@ -42,6 +42,7 @@ type RemoveTeamMemberCommand struct { type GetTeamMembersQuery struct { OrgId int64 TeamId int64 + UserId int64 Result []*TeamMemberDTO } diff --git a/pkg/models/user_auth.go b/pkg/models/user_auth.go index 0ecd144d52c..162a4d867a9 100644 --- a/pkg/models/user_auth.go +++ b/pkg/models/user_auth.go @@ -19,6 +19,7 @@ type ExternalUserInfo struct { Email string Login string Name string + Groups []string OrgRoles map[int64]RoleType } @@ -70,3 +71,8 @@ type GetAuthInfoQuery struct { Result *UserAuth } + +type SyncTeamsCommand struct { + ExternalUser *ExternalUserInfo + User *User +} diff --git a/pkg/registry/registry.go b/pkg/registry/registry.go index 6bbc302c593..87fca27f6c1 100644 --- a/pkg/registry/registry.go +++ b/pkg/registry/registry.go @@ -4,6 +4,8 @@ import ( "context" "reflect" "sort" + + "github.com/grafana/grafana/pkg/services/sqlstore/migrator" ) type Descriptor struct { @@ -57,13 +59,21 @@ type CanBeDisabled interface { // BackgroundService should be implemented for services that have // long running tasks in the background. type BackgroundService interface { - // Run starts the background process of the service after `Init` have been called // on all services. The `context.Context` passed into the function should be used // to subscribe to ctx.Done() so the service can be notified when Grafana shuts down. Run(ctx context.Context) error } +// DatabaseMigrator allows the caller to add migrations to +// the migrator passed as argument +type DatabaseMigrator interface { + + // AddMigrations allows the service to add migrations to + // the database migrator. + AddMigration(mg *migrator.Migrator) +} + // IsDisabled takes an service and return true if its disabled func IsDisabled(srv Service) bool { canBeDisabled, ok := srv.(CanBeDisabled) diff --git a/pkg/services/sqlstore/migrations/team_mig.go b/pkg/services/sqlstore/migrations/team_mig.go index eb0641fbc32..9800d27f8ab 100644 --- a/pkg/services/sqlstore/migrations/team_mig.go +++ b/pkg/services/sqlstore/migrations/team_mig.go @@ -50,4 +50,5 @@ func addTeamMigrations(mg *Migrator) { mg.AddMigration("Add column email to team table", NewAddColumnMigration(teamV1, &Column{ Name: "email", Type: DB_NVarchar, Nullable: true, Length: 190, })) + } diff --git a/pkg/services/sqlstore/sqlstore.go b/pkg/services/sqlstore/sqlstore.go index b0edc1676e0..13d706b6198 100644 --- a/pkg/services/sqlstore/sqlstore.go +++ b/pkg/services/sqlstore/sqlstore.go @@ -132,6 +132,13 @@ func (ss *SqlStore) Init() error { migrator := migrator.NewMigrator(x) migrations.AddMigrations(migrator) + for _, descriptor := range registry.GetServices() { + sc, ok := descriptor.Instance.(registry.DatabaseMigrator) + if ok { + sc.AddMigration(migrator) + } + } + if err := migrator.Start(); err != nil { return fmt.Errorf("Migration failed err: %v", err) } diff --git a/pkg/services/sqlstore/team.go b/pkg/services/sqlstore/team.go index 7d53d114235..9378ca37f60 100644 --- a/pkg/services/sqlstore/team.go +++ b/pkg/services/sqlstore/team.go @@ -268,7 +268,15 @@ func GetTeamMembers(query *m.GetTeamMembersQuery) error { query.Result = make([]*m.TeamMemberDTO, 0) sess := x.Table("team_member") sess.Join("INNER", "user", fmt.Sprintf("team_member.user_id=%s.id", x.Dialect().Quote("user"))) - sess.Where("team_member.org_id=? and team_member.team_id=?", query.OrgId, query.TeamId) + if query.OrgId != 0 { + sess.Where("team_member.org_id=?", query.OrgId) + } + if query.TeamId != 0 { + sess.Where("team_member.team_id=?", query.TeamId) + } + if query.UserId != 0 { + sess.Where("team_member.user_id=?", query.UserId) + } sess.Cols("user.org_id", "team_member.team_id", "team_member.user_id", "user.email", "user.login") sess.Asc("user.login", "user.email") diff --git a/public/app/core/config.ts b/public/app/core/config.ts index e111d0d0e9f..83ef17662c8 100644 --- a/public/app/core/config.ts +++ b/public/app/core/config.ts @@ -22,6 +22,7 @@ class Settings { disableUserSignUp: boolean; loginHint: any; loginError: any; + enterprise: boolean; constructor(options) { var defaults = { diff --git a/public/app/features/org/partials/team_details.html b/public/app/features/org/partials/team_details.html index 3fce8b3c720..c5ac8bd37a3 100644 --- a/public/app/features/org/partials/team_details.html +++ b/public/app/features/org/partials/team_details.html @@ -1,22 +1,22 @@
-

Team Details

+

Team Details

Name -
-
- - Email - - This is optional and is primarily used for allowing custom team avatars. - - - -
+
+
+ + Email + + This is optional and is primarily used for allowing custom team avatars. + + + +
@@ -26,42 +26,80 @@

Team Members

- +
Add member - - + +
- - - - - - - - - - - - - - -
UsernameEmail
{{member.login}}{{member.email}} - - - -
-
- - This team has no members yet. - -
+ + + + Username + Email + + + + + + + + {{member.login}} + {{member.email}} + + + + + + + +
+ + This team has no members yet. + +
+
+ +
+ +

Team Group Mapping

+
+
+ Add group + +
+
+ +
+
+ + + + + + + + + + + + +
Group
{{group.groupId}} + + + +
+
+ + This team has no associated groups yet. + +
+ +
diff --git a/public/app/features/org/team_details_ctrl.ts b/public/app/features/org/team_details_ctrl.ts index 3d193880635..35fa04f55d3 100644 --- a/public/app/features/org/team_details_ctrl.ts +++ b/public/app/features/org/team_details_ctrl.ts @@ -1,15 +1,21 @@ import coreModule from 'app/core/core_module'; +import config from 'app/core/config'; export default class TeamDetailsCtrl { team: Team; teamMembers: User[] = []; navModel: any; + teamGroups: TeamGroup[] = []; + newGroupId: string; + enterprise: boolean; /** @ngInject **/ constructor(private $scope, private backendSrv, private $routeParams, navModelSrv) { this.navModel = navModelSrv.getNav('cfg', 'teams', 0); this.userPicked = this.userPicked.bind(this); this.get = this.get.bind(this); + this.newGroupId = ''; + this.enterprise = config.enterprise; this.get(); } @@ -18,9 +24,16 @@ export default class TeamDetailsCtrl { this.backendSrv.get(`/api/teams/${this.$routeParams.id}`).then(result => { this.team = result; }); + this.backendSrv.get(`/api/teams/${this.$routeParams.id}/members`).then(result => { this.teamMembers = result; }); + + if (config.enterprise) { + this.backendSrv.get(`/api/teams/${this.$routeParams.id}/groups`).then(result => { + this.teamGroups = result; + }); + } } } @@ -57,6 +70,20 @@ export default class TeamDetailsCtrl { this.get(); }); } + + addGroup() { + this.backendSrv.post(`/api/teams/${this.$routeParams.id}/groups`, { groupId: this.newGroupId }).then(() => { + this.get(); + }); + } + + removeGroup(group: TeamGroup) { + this.backendSrv.delete(`/api/teams/${this.$routeParams.id}/groups/${group.groupId}`).then(this.get); + } +} + +export interface TeamGroup { + groupId: string; } export interface Team { diff --git a/scripts/build/build_enterprise.sh b/scripts/build/build_enterprise.sh index 02d8c78c885..cda3952c36a 100755 --- a/scripts/build/build_enterprise.sh +++ b/scripts/build/build_enterprise.sh @@ -14,9 +14,9 @@ cd /go/src/github.com/grafana/grafana echo "current dir: $(pwd)" cd .. -git clone -b ee_build --single-branch git@github.com:grafana/grafana-enterprise.git --depth 10 +git clone -b master --single-branch git@github.com:grafana/grafana-enterprise.git --depth 10 cd grafana-enterprise -git checkout 7fbae9c1be3467c4a39cf6ad85278a6896ceb49f +#git checkout 7fbae9c1be3467c4a39cf6ad85278a6896ceb49f ./build.sh cd ../grafana