feat: user v2 service query (#7095)

* feat: add query endpoints for user v2 api

* fix: correct integration tests

* fix: correct linting

* fix: correct linting

* fix: comment out permission check on user get and list

* fix: permission check on user v2 query

* fix: merge back origin/main

* fix: add search query in user emails

* fix: reset count for SearchUser if users are removed due to permissions

* fix: reset count for SearchUser if users are removed due to permissions

---------

Co-authored-by: Elio Bischof <elio@zitadel.com>
This commit is contained in:
Stefan Benz
2024-01-17 10:00:10 +01:00
committed by GitHub
parent 853181155d
commit d9d376a275
18 changed files with 1667 additions and 45 deletions

View File

@@ -97,3 +97,26 @@ message ListDetails {
}
];
}
enum TextQueryMethod {
TEXT_QUERY_METHOD_EQUALS = 0;
TEXT_QUERY_METHOD_EQUALS_IGNORE_CASE = 1;
TEXT_QUERY_METHOD_STARTS_WITH = 2;
TEXT_QUERY_METHOD_STARTS_WITH_IGNORE_CASE = 3;
TEXT_QUERY_METHOD_CONTAINS = 4;
TEXT_QUERY_METHOD_CONTAINS_IGNORE_CASE = 5;
TEXT_QUERY_METHOD_ENDS_WITH = 6;
TEXT_QUERY_METHOD_ENDS_WITH_IGNORE_CASE = 7;
}
enum ListQueryMethod {
LIST_QUERY_METHOD_IN = 0;
}
enum TimestampQueryMethod {
TIMESTAMP_QUERY_METHOD_EQUALS = 0;
TIMESTAMP_QUERY_METHOD_GREATER = 1;
TIMESTAMP_QUERY_METHOD_GREATER_OR_EQUALS = 2;
TIMESTAMP_QUERY_METHOD_LESS = 3;
TIMESTAMP_QUERY_METHOD_LESS_OR_EQUALS = 4;
}

View File

@@ -38,7 +38,6 @@ message HumanEmail {
bool is_verified = 2;
}
message SendEmailVerificationCode {
optional string url_template = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},

View File

@@ -0,0 +1,220 @@
syntax = "proto3";
package zitadel.user.v2beta;
option go_package = "github.com/zitadel/zitadel/pkg/grpc/user/v2beta;user";
import "google/api/annotations.proto";
import "google/api/field_behavior.proto";
import "protoc-gen-openapiv2/options/annotations.proto";
import "validate/validate.proto";
import "zitadel/user/v2beta/user.proto";
import "zitadel/object/v2beta/object.proto";
message SearchQuery {
oneof query {
option (validate.required) = true;
UserNameQuery user_name_query = 1;
FirstNameQuery first_name_query = 2;
LastNameQuery last_name_query = 3;
NickNameQuery nick_name_query = 4;
DisplayNameQuery display_name_query = 5;
EmailQuery email_query = 6;
StateQuery state_query = 7;
TypeQuery type_query = 8;
LoginNameQuery login_name_query = 9;
InUserIDQuery in_user_ids_query = 10;
OrQuery or_query = 11;
AndQuery and_query = 12;
NotQuery not_query = 13;
InUserEmailsQuery in_user_emails_query = 14;
}
}
message OrQuery {
repeated SearchQuery queries = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "the sub queries to 'OR'"
}
];
}
message AndQuery {
repeated SearchQuery queries = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "the sub queries to 'AND'"
}
];
}
message NotQuery {
SearchQuery query = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "the sub query to negate (NOT)"
}
];
}
message InUserIDQuery {
repeated string user_ids = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "the ids of the users to include"
example: "[\"69629023906488334\",\"69622366012355662\"]";
}
];
}
message UserNameQuery {
string user_name = 1 [
(validate.rules).string = {max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
max_length: 200;
example: "\"gigi-giraffe\"";
}
];
zitadel.object.v2beta.TextQueryMethod method = 2 [
(validate.rules).enum.defined_only = true,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines which text equality method is used";
}
];
}
message FirstNameQuery {
string first_name = 1 [
(validate.rules).string = {max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
max_length: 200;
example: "\"Gigi\"";
}
];
zitadel.object.v2beta.TextQueryMethod method = 2 [
(validate.rules).enum.defined_only = true,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines which text equality method is used";
}
];
}
message LastNameQuery {
string last_name = 1 [
(validate.rules).string = {max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
max_length: 200;
example: "\"Giraffe\"";
}
];
zitadel.object.v2beta.TextQueryMethod method = 2 [
(validate.rules).enum.defined_only = true,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines which text equality method is used";
}
];
}
message NickNameQuery {
string nick_name = 1 [(validate.rules).string = {max_len: 200}];
zitadel.object.v2beta.TextQueryMethod method = 2 [
(validate.rules).enum.defined_only = true,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines which text equality method is used";
}
];
}
message DisplayNameQuery {
string display_name = 1 [
(validate.rules).string = {max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
max_length: 200;
example: "\"Gigi Giraffe\"";
}
];
zitadel.object.v2beta.TextQueryMethod method = 2 [
(validate.rules).enum.defined_only = true,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines which text equality method is used";
}
];
}
message EmailQuery {
string email_address = 1 [
(validate.rules).string = {max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "email address of the user. (spec: https://tools.ietf.org/html/rfc2822#section-3.4.1)"
max_length: 200;
example: "\"gigi@zitadel.com\"";
}
];
zitadel.object.v2beta.TextQueryMethod method = 2 [
(validate.rules).enum.defined_only = true,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines which text equality method is used";
}
];
}
message LoginNameQuery {
string login_name = 1 [
(validate.rules).string = {max_len: 200},
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
max_length: 200;
example: "\"gigi@zitadel.cloud\"";
}
];
zitadel.object.v2beta.TextQueryMethod method = 2 [
(validate.rules).enum.defined_only = true,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "defines which text equality method is used";
}
];
}
//UserStateQuery always equals
message StateQuery {
UserState state = 1 [
(validate.rules).enum.defined_only = true,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "current state of the user";
}
];
}
//UserTypeQuery always equals
message TypeQuery {
Type type = 1 [
(validate.rules).enum.defined_only = true,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "the type of the user";
}
];
}
message InUserEmailsQuery {
repeated string user_emails = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "the emails of the users to include"
example: "[\"test@example.com\",\"test@example.org\"]";
}
];
}
enum Type {
TYPE_UNSPECIFIED = 0;
TYPE_HUMAN = 1;
TYPE_MACHINE = 2;
}
enum UserFieldName {
USER_FIELD_NAME_UNSPECIFIED = 0;
USER_FIELD_NAME_USER_NAME = 1;
USER_FIELD_NAME_FIRST_NAME = 2;
USER_FIELD_NAME_LAST_NAME = 3;
USER_FIELD_NAME_NICK_NAME = 4;
USER_FIELD_NAME_DISPLAY_NAME = 5;
USER_FIELD_NAME_EMAIL = 6;
USER_FIELD_NAME_STATE = 7;
USER_FIELD_NAME_TYPE = 8;
USER_FIELD_NAME_CREATION_DATE = 9;
}

View File

@@ -58,7 +58,7 @@ message SetHumanProfile {
example: "\"en\"";
}
];
optional zitadel.user.v2beta.Gender gender = 6 [
optional Gender gender = 6 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"GENDER_FEMALE\"";
}
@@ -98,11 +98,17 @@ message HumanProfile {
example: "\"en\"";
}
];
optional zitadel.user.v2beta.Gender gender = 6 [
optional Gender gender = 6 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"GENDER_FEMALE\"";
}
];
string avatar_url = 7 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "avatar URL of the user"
example: "\"https://api.zitadel.ch/assets/v1/avatar-32432jkh4kj32\"";
}
];
}
message SetMetadataEntry {
@@ -158,12 +164,79 @@ message HumanUser {
HumanPhone phone = 8;
}
message User {
string user_id = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"d654e6ba-70a3-48ef-a95d-37c8d8a7901a\"";
}
];
UserState state = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "current state of the user";
}
];
string username = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"minnie-mouse\"";
}
];
repeated string login_names = 4 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "[\"gigi@zitadel.com\", \"gigi@zitadel.zitadel.ch\"]";
}
];
string preferred_login_name = 5 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"gigi@zitadel.com\"";
}
];
oneof type {
HumanUser human = 6 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "one of type use human or machine"
}
];
MachineUser machine = 7 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "one of type use human or machine"
}
];
}
}
message MachineUser {
string name = 1 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"zitadel\"";
}
];
string description = 2 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"The one and only IAM\"";
}
];
bool has_secret = 3 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
example: "\"true\"";
}
];
AccessTokenType access_token_type = 4 [
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
description: "Type of access token to receive";
}
];
}
enum AccessTokenType {
ACCESS_TOKEN_TYPE_BEARER = 0;
ACCESS_TOKEN_TYPE_JWT = 1;
}
enum UserState {
USER_STATE_UNSPECIFIED = 0;
USER_STATE_ACTIVE = 1;
USER_STATE_INACTIVE = 2;
USER_STATE_DELETED = 3;
USER_STATE_LOCKED = 4;
USER_STATE_SUSPEND = 5;
USER_STATE_INITIAL = 6;
USER_STATE_INITIAL = 5;
}

View File

@@ -10,6 +10,7 @@ import "zitadel/user/v2beta/phone.proto";
import "zitadel/user/v2beta/idp.proto";
import "zitadel/user/v2beta/password.proto";
import "zitadel/user/v2beta/user.proto";
import "zitadel/user/v2beta/query.proto";
import "google/api/annotations.proto";
import "google/api/field_behavior.proto";
import "google/protobuf/duration.proto";
@@ -138,6 +139,73 @@ service UserService {
};
}
rpc GetUserByID(GetUserByIDRequest) returns (GetUserByIDResponse) {
option (google.api.http) = {
get: "/v2beta/users/{user_id}"
};
option (zitadel.protoc_gen_zitadel.v2.options) = {
auth_option: {
permission: "authenticated"
}
http_response: {
success_code: 200
}
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "User by ID";
description: "Returns the full user object (human or machine) including the profile, email, etc."
tags: "Users";
responses: {
key: "200"
value: {
description: "OK";
}
};
};
}
rpc ListUsers(ListUsersRequest) returns (ListUsersResponse) {
option (google.api.http) = {
post: "/v2beta/users"
body: "*"
};
option (zitadel.protoc_gen_zitadel.v2.options) = {
auth_option: {
permission: "authenticated"
}
http_response: {
success_code: 200
}
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
tags: "Users";
summary: "Search Users";
description: "Search for users. By default, we will return users of your organization. Make sure to include a limit and sorting for pagination."
responses: {
key: "200";
value: {
description: "A list of all users matching the query";
};
};
responses: {
key: "400";
value: {
description: "invalid list query";
schema: {
json_schema: {
ref: "#/definitions/rpcStatus";
};
};
};
};
};
}
// Change the email of a user
rpc SetEmail (SetEmailRequest) returns (SetEmailResponse) {
option (google.api.http) = {
@@ -368,7 +436,6 @@ service UserService {
}
};
option (grpc.gateway.protoc_gen_openapiv2.options.openapiv2_operation) = {
summary: "Delete user";
description: "The state of the user will be changed to 'deleted'. The user will not be able to log in anymore. Endpoints requesting this user will return an error 'User not found"
@@ -829,6 +896,40 @@ message AddHumanUserResponse {
optional string phone_code = 4;
}
message GetUserByIDRequest {
string user_id = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},
(google.api.field_behavior) = REQUIRED,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
min_length: 1;
max_length: 200;
example: "\"69629012906488334\"";
description: "User ID of the user you like to get."
}
];
zitadel.object.v2beta.Organization organization = 2;
}
message GetUserByIDResponse {
zitadel.object.v2beta.Details details = 1;
zitadel.user.v2beta.User user = 2;
}
message ListUsersRequest {
//list limitations and ordering
zitadel.object.v2beta.ListQuery query = 1;
// the field the result is sorted
zitadel.user.v2beta.UserFieldName sorting_column = 2;
//criteria the client is looking for
repeated zitadel.user.v2beta.SearchQuery queries = 3;
}
message ListUsersResponse {
zitadel.object.v2beta.ListDetails details = 1;
zitadel.user.v2beta.UserFieldName sorting_column = 2;
repeated zitadel.user.v2beta.User result = 3;
}
message SetEmailRequest{
string user_id = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},
@@ -962,24 +1063,6 @@ message DeleteUserResponse {
zitadel.object.v2beta.Details details = 1;
}
message GetUserByIDRequest {
string user_id = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},
(google.api.field_behavior) = REQUIRED,
(grpc.gateway.protoc_gen_openapiv2.options.openapiv2_field) = {
min_length: 1;
max_length: 200;
example: "\"69629012906488334\"";
description: "User ID of the user you like to get."
}
];
}
message GetUserByIDResponse {
zitadel.object.v2beta.Details details = 1;
HumanUser user = 2;
}
message UpdateHumanUserRequest{
string user_id = 1 [
(validate.rules).string = {min_len: 1, max_len: 200},