Monday, April 20, 2015

LDAP Controls description

LDAP controls were defined in the LDAPv3 specification as a way to extend LDAP and its operations without breaking the protocol. Many controls have been implemented, some of which are used when searching the directory (e.g., paged searching, VLV, finding deleted objects, and attribute scoped query), and some are needed to do certain modifications to the directory (e.g., cross-domain object moves, tree delete, and permissive modify). Controls can be marked as critical, which means they must be processed with the request, or an error is returned. If an unsupported control is not flagged as critical, the server can continue to process the request and ignore the control.
The complete list of controls supported by Active Directory is included in Table 4-2.

LDAP controls supported by Active Directory
Name
OID
Description
Paged Results
1.2.840.113556.1.4.319
Instructs the server to return search results in “pages.”
Cross Domain Move
1.2.840.113556.1.4.521
Used to move objects between domains.
DIRSYNC
1.2.840.113556.1.4.841
Used to find objects that have changed over a period of time.
Domain Scope
1.2.840.113556.1.4.1339
Informs the server to not generate any referrals in a search response.
Extended DN
1.2.840.113556.1.4.529
Used to return an object’s GUID and SID (for security principals) as part of its distinguished name.
Lazy Commit
1.2.840.113556.1.4.619
Informs the server to return after directory modifications have been written to memory, but before they have been written to disk. This can speed up processing of a lot of modifications.
Change Notification
1.2.840.113556.1.4.528
Used by clients to register for notification of when changes occur in the directory.
Permissive Modify
1.2.840.113556.1.4.1413
Allows duplicate adds of the same value for an attribute or deletion of an attribute that has no values to succeed (normally, it would fail in that situation).
SD Flags
1.2.840.113556.1.4.801
Used to pass flags to the server to control certain security descriptor options.
Search Options
1.2.840.113556.1.4.1340
Used to pass flags to the server to control search options.
Show Deleted Objects
1.2.840.113556.1.4.417
Used to inform the server to return any deleted objects that matched the search criteria.
Server-side Sort Request
1.2.840.113556.1.4.473
Used to inform the server to sort the results of a search.
Server-side Sort Response
1.2.840.113556.1.4.474
Returned by the server in response to a sort request.
Tree Delete
1.2.840.113556.1.4.805
Used to delete portions of the directory tree, including any child objects.
Verify Name
1.2.840.113556.1.4.1338
Used to target a specific GC server that is used to verify DN-valued attributes that are processed during add or modification operations.
VLV Request
2.16.840.1.113730.3.4.9
Used to request a virtual list view of results from a search. This control is new to Windows Server 2003.
VLV Response
2.16.840.1.113730.3.4.10
Response from server returning a virtual list view of results from a search. This control is new to Windows Server 2003.
Attribute Scoped Query
1.2.840.113556.1.4.1504
Used to force a query to be based on a specific DN-valued attribute. This control is new to Windows Server 2003. SeeRecipe 4.8 for an example.
Search Stats
1.2.840.113556.1.4.970
Used to return statistics about an LDAP query. See Recipe 15.9for an example.
Incremental Multivalue Retrieval
1.2.840.113556.1.4.802
Retrieve a range of values for a multi-valued attribute instead of all values at once. This control is new to Windows Server 2003.

See Also

RFC 2251 (Lightweight Directory Access Protocol (v3)) for a description of LDAP controls, MSDN: Extended Controls, and MSDN : Using Controls

Tuesday, April 7, 2015

God forks rails... why?


Go/Martini, структура проекта. С чего начать

Статья с хабра, весьма полезная. 

GopherЦель этой статьи — рассказать о языке программирования Go (Golang) тем разработчикам, которые смотрят в сторону этого языка, но еще не решились взяться за его изучение. Рассказ будет вестись на примере реального приложения, которое представляет из себя RESTful API веб-сервис.

Передо мной стояла задача разработать бэкэнд к мобильному сервису. Суть сервиса довольно проста. Мобильное приложение, которое показывает посты пользователей, находящихся рядом с текущим местоположением. На посты пользователи могут оставлять свои комментарии, которые тоже, в свою очередь, можно комментировать. Получается своеобразный гео-форум.

Давно хотел попробовать применить язык Go для сколь нибудь серьезных проектов. Выбор был очевиден, благо что этот язык как нельзя лучше подходит для подобных задач.

Основные преимущества языка Go:

  • Простой и понятный синтаксис. Это делает написание кода приятным занятием.
  • Статическая типизация. Позволяет избежать ошибок, допущенных по невнимательности, упрощает чтение и понимание кода, делает код однозначным.
  • Скорость и компиляция. Скорость у Go в десятки раз быстрее, чем у скриптовых языков, при меньшем потреблении памяти. При этом, компиляция практически мгновенна. Весь проект компилируется в один бинарный файл, без зависимостей. Как говорится, «просто добавь воды». И вам не надо заботиться о памяти, есть сборщик мусора.
  • Отход от ООП. В языке нет классов, но есть структуры данных с методами. Наследование заменяется механизмом встраивания. Существуют интерфейсы, которые не нужно явно имплементировать, а лишь достаточно реализовать методы интерфейса.
  • Параллелизм. Параллельные вычисления в языке делаются просто, изящно и без головной боли. Горутины (что-то типа потоков) легковесны, потребляют мало памяти.
  • Богатая стандартная библиотека. В языке есть все необходимое для веб-разработки и не только. Количество сторонних библиотек постоянно растет. Кроме того, есть возможность использовать библиотеки C и C++.
  • Возможность писать в функциональном стиле. В языке есть замыкания (closures) и анонимные функции. Функции являются объектами первого порядка, их можно передавать в качестве аргументов и использовать в качестве типов данных.
  • Авторитетные отцы-основатели и сильное комьюнити. Роб Пайк, Кен Томпсон, Роберт Гризмер стояли у истоков. Сейчас у языка более 300 контрибьюторов. Язык имеет сильное сообщество и постоянно развивается.
  • Open Source
  • Обаятельный талисман


Все эти, и многие другие особенности позволяют выделить язык среди остальных. Это достойный кандидат на изучение, к тому же, освоить язык довольно просто.

Итак, вернемся к нашей задаче. Хоть язык и не накладывает ограничений на структуру проекта, данное приложение я решил организовать по модели MVC. Правда View реализовывается на стороне клиента. В моем случае это был AngularJS, в перспективе — нативное мобильное приложение. Здесь я расскажу лишь об API на стороне сервиса.

Структура проекта получилась следующая:

/project/
    /conf/
        errors.go
        settings.go
    /controllers/
        posts.go
        users.go
    /models/
        posts.go
        users.go
    /utils/
        helpers.go
    loctalk.go


Программа в Go разделяется на пакеты (package), что указывается в начале каждого файла. Имя пакета должно соответствовать директории в которой находятся файлы, входящие в пакет. Так же, должен быть главный пакет main с функцией main(). Он у меня находится в корневом файле приложения loctalk.go. Таким образом, у меня получилось 5 пакетов: conf, controllers, models, utils, main.
Буду приводить неполное содержание файлов, а только минимально необходимое для понимания.

Пакет conf содержит константы и настройки сайта. 

package conf

import (
    "os"
)

const (
    SITE_NAME string = "LocTalk"
    DEFAULT_LIMIT  int = 10
    MAX_LIMIT      int = 1000
    MAX_POST_CHARS int = 1000
)
func init() {
    mode := os.Getenv("MARTINI_ENV")

    switch mode {
    case "production":
        SiteUrl = "http://loctalk.net"
        AbsolutePath = "/path/to/project/"
    default:
        SiteUrl = "http://127.0.0.1"
        AbsolutePath = "/path/to/project/"
    }
}


Думаю, комментировать тут нечего. Функция init() вызывается в каждом пакете до вызова main(). Их может быть несколько в разных файлах.

Пакет main.

package main

import (
    "github.com/go-martini/martini"
    "net/http"
    "loctalk/conf"
    "loctalk/controllers"
    "loctalk/models"
    "loctalk/utils"
)

func main() {
    m := martini.Classic()

    m.Use(func(w http.ResponseWriter) {
        w.Header().Set("Content-Type", "application/json; charset=utf-8")
    })

    m.Map(new(utils.MarshUnmarsh))

    Auth := func(mu *utils.MarshUnmarsh, req *http.Request, rw http.ResponseWriter) {
        reqUserId := req.Header.Get("X-Auth-User")
        reqToken := req.Header.Get("X-Auth-Token")
        if !models.CheckToken(reqUserId, reqToken) {
            rw.WriteHeader(http.StatusUnauthorized)
            rw.Write(mu.Marshal(conf.ErrUserAccessDenied))
        }
    }

    // ROUTES
    m.Get("/", controllers.Home)

    // users
    m.Get("/api/v1/users", controllers.GetUsers)
    m.Get("/api/v1/users/:id", controllers.GetUserById)
    m.Post("/api/v1/users", controllers.CreateUser)
    // …

    // posts
    m.Get("/api/v1/posts", controllers.GetRootPosts)
    m.Get("/api/v1/posts/:id", controllers.GetPostById)
    m.Post("/api/v1/posts", Auth, controllers.CreatePost)
    // ...

    m.Run()
}


В самом верху определяется имя пакета. Далее идет список импортируемых пакетов. Мы будем использовать пакет Martini. Он добавляет легкую прослойку для быстрого и удобного создания веб-приложений. Обратите внимание как импортируется этот пакет. Нужно указать путь к репозиторию откуда он был взят. А чтобы его получить, достаточно в консоли набрать команду go get github.com/go-martini/martini

Далее мы создаем экземпляр Martini, настраиваем и запускаем его. Обратите внимание на знак « := ». Это сокращенный синтаксис, он означает: создать переменную соответствующего типа и инициализировать ее. Например, написав a := «hello», мы создадим переменную a типа string и присвоим ей строку «hello».

Переменная m в нашем случае имеет тип *ClassicMartini, именно это возвращает martini.Classic(). * означает указатель, т. е. передается не само значение, а лишь указатель на него. В метод m.Use() мы передаем функцию-обработчик. Этот Middleware позволяет Martini делать определенные действия над каждым запросом. В данном случае, мы определяем Content-Type для каждого запроса. Метод m.Map() же позволяет привязать нашу структуру и использовать ее затем в контроллерах при необходимости (механизм dependency injection). В данном случае, я создал обертку для кодирования структуры данных в формат json.

Тут же мы создаем внутреннюю функцию Auth, которая проверяет авторизацию пользователя. Ее можно вставить в наши роуты и она будет вызываться до вызова контроллера. Эти вещи возможны благодаря Martini. С использованием стандартной библиотеки код получился бы немного другой.

Взглянем на файл errors.go пакета conf. 

package conf

import (
    "fmt"
    "net/http"
)

type ApiError struct {
    Code        int    `json:"errorCode"`
    HttpCode    int    `json:"-"`
    Message     string `json:"errorMsg"`
    Info        string `json:"errorInfo"`
}

func (e *ApiError) Error() string {
    return e.Message
}

func NewApiError(err error) *ApiError {
    return &ApiError{0, http.StatusInternalServerError, err.Error(), ""}
}

var ErrUserPassEmpty = &ApiError{110, http.StatusBadRequest, "Password is empty", ""}
var ErrUserNotFound = &ApiError{123, http.StatusNotFound, "User not found", ""}
var ErrUserIdEmpty = &ApiError{130, http.StatusBadRequest, "Empty User Id", ""}
var ErrUserIdWrong = &ApiError{131, http.StatusBadRequest, "Wrong User Id", ""}
// … и т. д. 


Язык поддерживает возврат нескольких значений. Вместо механизма try-catch, очень часто используется прием, когда вторым аргументом возвращается ошибка. И при ее наличии, она обрабатывается. Есть встроенный тип error, который представляет из себя интерфейс:

type error interface {
    Error() string
}


Таким образом, чтобы реализовать этот интерфейс, достаточно иметь метод Error() string. Я создал свой тип для ошибок ApiError, который более специфичен для моих задач, однако совместим со встроенным типом error.

Обратите внимание на — type ApiError struct. Это определение структуры, модели данных, которую вы будете использовать постоянно в своей работе. Она состоит из полей определенных типов (надеюсь, вы успели заметить, что тип данных пишется после имени переменной). Кстати, полями могут быть другие структуры, наследуя все методы и поля. В одинарных кавычках `` указаны теги. Их указывать не обязательно. В данном случае они используются пакетом encoding/json для указания имени в выводе json (знак минус «-» вообще исключает поле из вывода). 

Обратите внимание, что поля структуры написаны с заглавной буквы. Это означает, что они имеют область видимости за пределами пакета. Если написать их с прописной буквы, они экспортироваться не будут, а будут доступны только в пределах пакета. Это же относится и к функциям и методам. Вот такой простой механизм инкапсуляции.

Двигаемся дальше. Определение func (e *ApiError) Error() string означает ни что иное, как метод данной структуры. Переменная e — это указатель на структуру, своего рода self/this. Соответственно вызвав метод .Error() на структуре, мы получим ее поле Message. 

Далее мы определяем предустановленные ошибки и заполняем их поля. Поля вида http.StatusBadRequest — это значения типа int в пакете http для стандартных кодов ответа, своего рода алиасы. Мы используем сокращенный синтаксис объявления структуры &ApiError{} с инициализацией. По другому можно было бы написать так:

MyError := new(ApiError)
MyError.Code = 110
// …


Символ & означает получить указатель на данную структуру. Оператор new() так же возвращает указатель, а не значение. По-началу возникает небольшая путаница с указателями, но, со временем, вы привыкните.

Перейдем к нашим моделям. Приведу урезанную версию модели постов:

package models

import (
    "labix.org/v2/mgo/bson"
    "loctalk/conf"
    "loctalk/utils"
    "time"
    "unicode/utf8"
    "log"
)

// GeoJSON format
type Geo struct {
    Type        string     `json:"-"`          
    Coordinates [2]float64 `json:"coordinates"`
}

type Post struct {
    Id         bson.ObjectId `json:"id" bson:"_id,omitempty"`
    UserId     bson.ObjectId `json:"userId"`
    UserName   string   `json:"userName"`
    ThumbUrl   string   `json:"thumbUrl"`
    ParentId   bson.ObjectId `json:"parentId,omitempty" bson:",omitempty"`
    Enabled    bool          `json:"-"`
    Body       string        `json:"body"`
    Geo        Geo           `json:"geo"`
    Date       time.Time     `json:"date" bson:",omitempty"`
}

func NewPost() *Post {
    return new(Post)
}

func (p *Post) LoadById(id string) *conf.ApiError {
    if !bson.IsObjectIdHex(id) {
        return conf.ErrPostIdWrong
    }

    session := utils.NewDbSession()
    defer session.Close()
    c := session.Col("posts")
    err := c.Find(bson.M{"_id": bson.ObjectIdHex(id), "enabled": true}).One(p)
    if p.Id == "" {
        return conf.ErrPostNotFound
    }
    if err != nil {
        return conf.NewApiError(err)
    }
    return nil
}

func (p *Post) Create() (id string, err *conf.ApiError) {
    // validation
    switch {
    case p.UserId == "":
        err = conf.ErrUserIdEmpty
    case p.Body == "":
        err = conf.ErrPostBodyEmpty
    case utf8.RuneCountInString(p.Body) > conf.MAX_POST_CHARS:
        err = conf.ErrPostMaxSize
    case p.Geo.Coordinates[0] == 0.0 || p.Geo.Coordinates[1] == 0.0:
        err = conf.ErrPostLocationEmpty
    }
    if err != nil {
        return
    }

    p.Id = bson.NewObjectId()
    p.Geo.Type = "Point"
    p.Enabled = true
    p.Date = time.Now()

    session := utils.NewDbSession()
    defer session.Close()

    c := session.Col("posts")
    errDb := c.Insert(p)

    if errDb != nil {
        return "", conf.NewApiError(errDb)
    }

    return p.Id.Hex(), nil
}

func (p *Post) Update() *conf.ApiError {
    session := utils.NewDbSession()
    defer session.Close()
    c := session.Col("posts")
    err := c.UpdateId(p.Id, p)
    if err != nil {
        return conf.NewApiError(err)
    }
    return nil
}

func (p *Post) Disable() *conf.ApiError {
    session := utils.NewDbSession()
    defer session.Close()
    p.Enabled = false
    c := session.Col("posts")
    err := c.UpdateId(p.Id, p)
    if err != nil {
        return conf.NewApiError(err)
    }
    return nil
}

// … 


Здесь мы используем замечательный драйвер для MongoDb — mgo, чтобы сохранять данные. Для удобства, я создал небольшую обертку над api mgo — utils.NewDbSession. Логика работы с данными: сначала мы создаем объект во внутренней структуре языка, а затем, с помощью метода этой структуры, сохраняем его в базу данных. 

Обратите внимание, что в этих методах мы везде используем наш тип ошибки conf.ApiError. Стандартные ошибки мы конвертируем в наши с помощью conf.NewApiError(err). Так же, важен оператор defer. Он исполняется в самом конце выполнения метода. В данном случае, закрывает соединение с БД. 

Что ж, осталось взглянуть на контроллер, который обрабатывает запросы и выводит json в ответ.

package controllers

import (
    "encoding/json"
    "fmt"
    "github.com/go-martini/martini"
    "labix.org/v2/mgo/bson"
    "loctalk/conf"
    "loctalk/models"
    "loctalk/utils"
    "net/http"
)
func GetPostById(mu *utils.MarshUnmarsh, params martini.Params) (int, []byte) {
    id := params["id"]
    post := models.NewPost()
    err := post.LoadById(id)
    if err != nil {
        return err.HttpCode, mu.Marshal(err)
    }
    return http.StatusOK, mu.Marshal(post)
}

// ...


Здесь мы получаем из URL id запрашиваемого поста, создаем новый экземпляр нашей структуры и вызываем на ней метод LoadById(id) для загрузки данных из БД и заполнения данной структуры. Которую мы и выводим в HTTP ответ, предварительно преобразовав в json нашим методом mu.Marshal(post).

Обратите внимание на сигнатуру функции: 
func GetPostById(mu *utils.MarshUnmarsh, params martini.Params) (int, []byte)

Входные параметры нам предоставляет Martini с помощью механизма внедрения зависимостей (dependency injection). И мы возвращаем два параметра (int, []byte) — число (статус ответа) и массив байт.

Итак, мы разобрали основные компоненты и подходы, используя которые, вы сможете сделать эффективный RESTful API интерфейс в короткие сроки. Надеюсь, статья была полезна и вдохновит некоторых из вас заняться изучением замечательного языка Go. Уверен, за ним будущее.

Для изучения могу порекомендовать хорошую книгу на русском «Программирование на языке Go» Марка Саммерфильда. И, конечно, больше практиковаться.

UPD: Tour Go на русском.